@@ -3,7 +3,7 @@ import fs from "fs/promises";
3
3
import os from "os" ;
4
4
import path from "path" ;
5
5
import { performance } from "perf_hooks" ;
6
- import { Database as DatabaseType , Statement } from "better-sqlite3" ;
6
+ import { Database as DatabaseType } from "better-sqlite3" ;
7
7
import { z } from "zod" ;
8
8
import { Response } from "../../http" ;
9
9
import { HttpError , Log } from "../../shared" ;
@@ -135,22 +135,8 @@ function normaliseResults(
135
135
) ;
136
136
}
137
137
138
- const DOESNT_RETURN_DATA_MESSAGE =
139
- "The columns() method is only for statements that return data" ;
140
138
const EXECUTE_RETURNS_DATA_MESSAGE =
141
139
"SQL execute error: Execute returned results - did you mean to call query?" ;
142
- function returnsData ( stmt : Statement ) : boolean {
143
- try {
144
- stmt . columns ( ) ;
145
- return true ;
146
- } catch ( e ) {
147
- // `columns()` fails on statements that don't return data
148
- if ( e instanceof TypeError && e . message === DOESNT_RETURN_DATA_MESSAGE ) {
149
- return false ;
150
- }
151
- throw e ;
152
- }
153
- }
154
140
155
141
const CHANGES_QUERY = "SELECT total_changes() AS totalChanges" ;
156
142
const CHANGES_LAST_ROW_QUERY =
@@ -167,8 +153,30 @@ interface ChangesLastRowResult {
167
153
export class D1Gateway {
168
154
private readonly db : DatabaseType ;
169
155
170
- constructor ( private readonly log : Log , private readonly storage : Storage ) {
171
- this . db = storage . getSqliteDatabase ( ) ;
156
+ constructor ( private readonly log : Log , legacyStorage : Storage ) {
157
+ const storage = legacyStorage . getNewStorage ( ) ;
158
+ this . db = storage . db ;
159
+ }
160
+
161
+ #prepareAndBind( query : D1SingleQuery ) {
162
+ // D1 only respects the first statement
163
+ const sql = splitSqlQuery ( query . sql ) [ 0 ] ;
164
+ const stmt = this . db . prepare ( sql ) ;
165
+ const params = normaliseParams ( query . params ) ;
166
+ if ( params . length === 0 ) return stmt ;
167
+
168
+ try {
169
+ return stmt . bind ( params ) ;
170
+ } catch ( e ) {
171
+ // For statements using ?1, ?2, etc, we want to pass them as an array but
172
+ // `better-sqlite3` expects an object with the shape:
173
+ // `{ 1: params[0], 2: params[1], ... }`. Try bind like that instead.
174
+ try {
175
+ return stmt . bind ( Object . fromEntries ( params . map ( ( v , i ) => [ i + 1 , v ] ) ) ) ;
176
+ } catch { }
177
+ // If that still failed, re-throw the original error
178
+ throw e ;
179
+ }
172
180
}
173
181
174
182
#getTotalChanges( ) : number | bigint {
@@ -181,18 +189,15 @@ export class D1Gateway {
181
189
182
190
#query: QueryRunner = ( query ) => {
183
191
const meta : OkMeta = { start : performance . now ( ) } ;
184
- // D1 only respects the first statement
185
- const sql = splitSqlQuery ( query . sql ) [ 0 ] ;
186
- const stmt = this . db . prepare ( sql ) ;
187
- const params = normaliseParams ( query . params ) ;
192
+ const stmt = this . #prepareAndBind( query ) ;
188
193
let results : Record < string , SqliteValue > [ ] ;
189
- if ( returnsData ( stmt ) ) {
194
+ if ( stmt . reader ) {
190
195
// `better-sqlite3` doesn't return `last_row_id` and `changes` from `all`.
191
196
// We need to make extra queries to get them, but we only want to return
192
197
// them if this `stmt` made changes. So check total changes before and
193
198
// after querying `stmt`.
194
199
const initialTotalChanges = this . #getTotalChanges( ) ;
195
- results = stmt . all ( params ) ;
200
+ results = stmt . all ( ) ;
196
201
const { totalChanges, changes, lastRowId } = this . #getChangesLastRow( ) ;
197
202
if ( totalChanges > initialTotalChanges ) {
198
203
meta . last_row_id = Number ( lastRowId ) ;
@@ -201,7 +206,7 @@ export class D1Gateway {
201
206
} else {
202
207
// `/query` does support queries that don't return data,
203
208
// returning `[]` instead of `null`
204
- const result = stmt . run ( params ) ;
209
+ const result = stmt . run ( ) ;
205
210
results = [ ] ;
206
211
meta . last_row_id = Number ( result . lastInsertRowid ) ;
207
212
meta . changes = result . changes ;
@@ -211,13 +216,10 @@ export class D1Gateway {
211
216
212
217
#execute: QueryRunner = ( query ) => {
213
218
const meta : OkMeta = { start : performance . now ( ) } ;
214
- // D1 only respects the first statement
215
- const sql = splitSqlQuery ( query . sql ) [ 0 ] ;
216
- const stmt = this . db . prepare ( sql ) ;
219
+ const stmt = this . #prepareAndBind( query ) ;
217
220
// `/execute` only supports queries that don't return data
218
- if ( returnsData ( stmt ) ) throw new Error ( EXECUTE_RETURNS_DATA_MESSAGE ) ;
219
- const params = normaliseParams ( query . params ) ;
220
- const result = stmt . run ( params ) ;
221
+ if ( stmt . reader ) throw new Error ( EXECUTE_RETURNS_DATA_MESSAGE ) ;
222
+ const result = stmt . run ( ) ;
221
223
meta . last_row_id = Number ( result . lastInsertRowid ) ;
222
224
meta . changes = result . changes ;
223
225
return ok ( null , meta ) ;
0 commit comments