Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit e49b9b3

Browse files
authored
Fix binding of ?N parameters in D1 prepare statements, closes #504 (#544)
`better-sqlite3` expects parameters of the form `?1, ?2, ...` to be bound as an object of the form `{ 1: params[0], 2: params[1], ...}`. In #480, we accidentally removed the code that handled this case. This PR adds it back, and lifts out some common functionality into a `#prepareAndBind()` function. :) Thanks @ruslantalpa for spotting the removed code. Closes #526 Closes cloudflare/workers-sdk#2811 Closes cloudflare/workers-sdk#2887
1 parent 015e1d6 commit e49b9b3

File tree

2 files changed

+36
-10
lines changed

2 files changed

+36
-10
lines changed

packages/d1/src/api.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import path from "path";
55
import { performance } from "perf_hooks";
66
import { Request, RequestInfo, RequestInit, Response } from "@miniflare/core";
77
import type { SqliteDB } from "@miniflare/shared";
8-
import type { Statement as SqliteStatement } from "better-sqlite3";
98
import splitSqlQuery from "./splitter";
109

1110
// query
@@ -98,19 +97,37 @@ const EXECUTE_RETURNS_DATA_MESSAGE =
9897
export class D1DatabaseAPI {
9998
constructor(private readonly db: SqliteDB) {}
10099

101-
#query: QueryRunner = (query) => {
102-
const meta: OkMeta = { start: performance.now() };
100+
#prepareAndBind(query: SingleQuery) {
103101
// D1 only respects the first statement
104102
const sql = splitSqlQuery(query.sql)[0];
105103
const stmt = this.db.prepare(sql);
106104
const params = normaliseParams(query.params);
105+
if (params.length === 0) return stmt;
106+
107+
try {
108+
return stmt.bind(params);
109+
} catch (e) {
110+
// For statements using ?1, ?2, etc, we want to pass them as an array but
111+
// `better-sqlite3` expects an object with the shape:
112+
// `{ 1: params[0], 2: params[1], ... }`. Try bind like that instead.
113+
try {
114+
return stmt.bind(Object.fromEntries(params.map((v, i) => [i + 1, v])));
115+
} catch {}
116+
// If that still failed, re-throw the original error
117+
throw e;
118+
}
119+
}
120+
121+
#query: QueryRunner = (query) => {
122+
const meta: OkMeta = { start: performance.now() };
123+
const stmt = this.#prepareAndBind(query);
107124
let results: any[];
108125
if (stmt.reader) {
109-
results = stmt.all(params);
126+
results = stmt.all();
110127
} else {
111128
// `/query` does support queries that don't return data,
112129
// returning `[]` instead of `null`
113-
const result = stmt.run(params);
130+
const result = stmt.run();
114131
results = [];
115132
meta.last_row_id = Number(result.lastInsertRowid);
116133
meta.changes = result.changes;
@@ -120,13 +137,10 @@ export class D1DatabaseAPI {
120137

121138
#execute: QueryRunner = (query) => {
122139
const meta: OkMeta = { start: performance.now() };
123-
// D1 only respects the first statement
124-
const sql = splitSqlQuery(query.sql)[0];
125-
const stmt = this.db.prepare(sql);
140+
const stmt = this.#prepareAndBind(query);
126141
// `/execute` only supports queries that don't return data
127142
if (stmt.reader) throw new Error(EXECUTE_RETURNS_DATA_MESSAGE);
128-
const params = normaliseParams(query.params);
129-
const result = stmt.run(params);
143+
const result = stmt.run();
130144
meta.last_row_id = Number(result.lastInsertRowid);
131145
meta.changes = result.changes;
132146
return ok(null, meta);

packages/d1/test/d1js.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,18 @@ test("D1PreparedStatement: bind", async (t) => {
175175
.bind("green")
176176
.all<ColourRow>();
177177
t.is(colourResults.results?.length, 1);
178+
179+
// Check with numbered parameters (execute and query)
180+
// https://github.com/cloudflare/miniflare/issues/504
181+
await db
182+
.prepare("INSERT INTO colours (id, name, rgb) VALUES (?3, ?1, ?2)")
183+
.bind("yellow", 0xffff00, 4)
184+
.run();
185+
const colourResult = await db
186+
.prepare("SELECT * FROM colours WHERE id = ?1")
187+
.bind(4)
188+
.first<ColourRow>();
189+
t.deepEqual(colourResult, { id: 4, name: "yellow", rgb: 0xffff00 });
178190
});
179191

180192
// Lots of strange edge cases here...

0 commit comments

Comments
 (0)