1
1
import {
2
2
PrepareOptions ,
3
- ResetOptions ,
3
+ QueryOptions ,
4
+ SqliteArrayRow ,
4
5
SqliteChanges ,
5
6
SqliteDriverConnection ,
6
7
SqliteDriverStatement ,
8
+ SqliteObjectRow ,
7
9
SqliteParameterBinding ,
8
- SqliteStepResult ,
9
10
SqliteValue ,
10
- StepOptions ,
11
+ StreamQueryOptions ,
11
12
UpdateListener
12
13
} from '@sqlite-js/driver' ;
13
14
import type * as bsqlite from 'better-sqlite3' ;
@@ -23,19 +24,13 @@ interface InternalStatement extends SqliteDriverStatement {
23
24
24
25
class BetterSqlitePreparedStatement implements InternalStatement {
25
26
public statement : bsqlite . Statement ;
26
- private options : PrepareOptions ;
27
- private bindPositional : SqliteValue [ ] = [ ] ;
28
- private bindNamed : Record < string , SqliteValue > = { } ;
29
- private statementDone = false ;
30
- private iterator : Iterator < unknown > | undefined = undefined ;
31
27
32
28
readonly persisted : boolean ;
33
29
34
30
[ Symbol . dispose ] : ( ) => void = undefined as any ;
35
31
36
32
constructor ( statement : bsqlite . Statement , options : PrepareOptions ) {
37
33
this . statement = statement ;
38
- this . options = options ;
39
34
this . persisted = options . autoFinalize ?? false ;
40
35
41
36
if ( typeof Symbol . dispose != 'undefined' ) {
@@ -57,140 +52,125 @@ class BetterSqlitePreparedStatement implements InternalStatement {
57
52
}
58
53
}
59
54
60
- bind ( parameters : SqliteParameterBinding ) : void {
61
- if ( parameters == null ) {
62
- return ;
63
- }
64
- if ( Array . isArray ( parameters ) ) {
65
- let bindArray = this . bindPositional ;
66
-
67
- for ( let i = 0 ; i < parameters . length ; i ++ ) {
68
- if ( typeof parameters [ i ] != 'undefined' ) {
69
- bindArray [ i ] = parameters [ i ] ! ;
70
- }
71
- }
72
- } else {
73
- for ( let key in parameters ) {
74
- const value = parameters [ key ] ;
75
- let name = key ;
76
- const prefix = key [ 0 ] ;
77
- // better-sqlite doesn't support the explicit prefix - strip it
78
- if ( prefix == ':' || prefix == '?' || prefix == '$' || prefix == '@' ) {
79
- name = key . substring ( 1 ) ;
80
- }
81
- this . bindNamed [ name ] = value ;
55
+ private checkTransaction ( options : QueryOptions | undefined ) {
56
+ if ( options ?. requireTransaction ) {
57
+ if ( ! this . statement . database . inTransaction ) {
58
+ throw new Error ( 'Transaction has been rolled back' ) ;
82
59
}
83
60
}
84
61
}
85
62
86
- async step ( n ?: number , options ?: StepOptions ) : Promise < SqliteStepResult > {
63
+ _all (
64
+ parameters : SqliteParameterBinding ,
65
+ options : QueryOptions | undefined ,
66
+ array : boolean
67
+ ) : unknown [ ] {
68
+ this . checkTransaction ( options ) ;
69
+
70
+ const statement = this . statement ;
71
+
72
+ statement . safeIntegers ( options ?. bigint ?? false ) ;
73
+ statement . raw ( array ) ;
74
+ const r = statement . all ( sanitizeParameters ( parameters ) ) ;
75
+ return r ;
76
+ }
77
+
78
+ async all (
79
+ parameters ?: SqliteParameterBinding ,
80
+ options ?: QueryOptions
81
+ ) : Promise < SqliteObjectRow [ ] > {
87
82
try {
88
- return this . stepSync ( n , options ) ;
83
+ return this . _all ( parameters , options , false ) as SqliteObjectRow [ ] ;
89
84
} catch ( e ) {
90
85
throw mapError ( e ) ;
91
86
}
92
87
}
93
88
94
- async run ( options ?: StepOptions ) : Promise < SqliteChanges > {
89
+ async allArray (
90
+ parameters ?: SqliteParameterBinding ,
91
+ options ?: QueryOptions
92
+ ) : Promise < SqliteArrayRow [ ] > {
95
93
try {
96
- return this . runSync ( options ) ;
94
+ return this . _all ( parameters , options , true ) as SqliteArrayRow [ ] ;
97
95
} catch ( e ) {
98
96
throw mapError ( e ) ;
99
97
}
100
98
}
101
99
102
- runSync ( options ?: StepOptions ) : SqliteChanges {
103
- if ( options ?. requireTransaction ) {
104
- if ( ! this . statement . database . inTransaction ) {
105
- throw new Error ( 'Transaction has been rolled back' ) ;
106
- }
107
- }
100
+ * _stream (
101
+ parameters : SqliteParameterBinding ,
102
+ options : StreamQueryOptions | undefined ,
103
+ array : boolean
104
+ ) {
105
+ this . checkTransaction ( options ) ;
108
106
109
107
const statement = this . statement ;
110
- this . reset ( ) ;
111
108
112
- try {
113
- const bindNamed = this . bindNamed ;
114
- const bindPositional = this . bindPositional ;
115
- const bind = [ bindPositional , bindNamed ] . filter ( ( b ) => b != null ) ;
116
-
117
- statement . safeIntegers ( true ) ;
118
- const r = statement . run ( ...bind ) ;
119
- return {
120
- changes : r . changes ,
121
- lastInsertRowId : r . lastInsertRowid as bigint
122
- } ;
123
- } finally {
124
- this . reset ( ) ;
109
+ statement . safeIntegers ( options ?. bigint ?? false ) ;
110
+ statement . raw ( array ) ;
111
+ const iter = statement . iterate ( sanitizeParameters ( parameters ) ) ;
112
+ const maxBuffer = options ?. chunkMaxRows ?? 100 ;
113
+ let buffer : any [ ] = [ ] ;
114
+ for ( let row of iter ) {
115
+ buffer . push ( row as any ) ;
116
+ if ( buffer . length >= maxBuffer ) {
117
+ yield buffer ;
118
+ buffer = [ ] ;
119
+ }
125
120
}
126
121
}
127
122
128
- stepSync ( n ?: number , options ?: StepOptions ) : SqliteStepResult {
129
- const all = n == null ;
130
-
131
- const statement = this . statement ;
132
- if ( this . statementDone ) {
133
- return { done : true } as SqliteStepResult ;
123
+ async * stream (
124
+ parameters ?: SqliteParameterBinding ,
125
+ options ?: StreamQueryOptions
126
+ ) : AsyncIterator < SqliteObjectRow [ ] > {
127
+ try {
128
+ yield * this . _stream ( parameters , options , false ) ;
129
+ } catch ( e ) {
130
+ throw mapError ( e ) ;
134
131
}
132
+ }
135
133
136
- if ( options ?. requireTransaction ) {
137
- if ( ! this . statement . database . inTransaction ) {
138
- throw new Error ( 'Transaction has been rolled back' ) ;
139
- }
134
+ async * streamArray (
135
+ parameters ?: SqliteParameterBinding ,
136
+ options ?: StreamQueryOptions
137
+ ) : AsyncIterator < SqliteArrayRow [ ] > {
138
+ try {
139
+ yield * this . _stream ( parameters , options , true ) ;
140
+ } catch ( e ) {
141
+ throw mapError ( e ) ;
140
142
}
143
+ }
141
144
142
- const bindNamed = this . bindNamed ;
143
- const bindPositional = this . bindPositional ;
144
- const bind = [ bindPositional , bindNamed ] . filter ( ( b ) => b != null ) ;
145
- if ( ! statement . reader ) {
146
- statement . run ( ...bind ) ;
147
- this . statementDone = true ;
148
- return { rows : [ ] , done : true } as SqliteStepResult ;
149
- }
150
- let iterator = this . iterator ;
151
- const num_rows = n ?? 1 ;
152
- if ( iterator == null ) {
153
- statement . raw ( this . options . rawResults ?? false ) ;
154
- statement . safeIntegers ( this . options . bigint ?? false ) ;
155
- iterator = statement . iterate ( ...bind ) ;
156
- this . iterator = iterator ;
157
- }
158
- let rows = [ ] ;
159
- let isDone = false ;
160
- for ( let i = 0 ; i < num_rows || all ; i ++ ) {
161
- const { value, done } = iterator . next ( ) ;
162
- if ( done ) {
163
- isDone = true ;
164
- break ;
165
- }
166
- rows . push ( value ) ;
167
- }
168
- if ( isDone ) {
169
- this . statementDone = true ;
145
+ async run (
146
+ parameters ?: SqliteParameterBinding ,
147
+ options ?: QueryOptions
148
+ ) : Promise < SqliteChanges > {
149
+ try {
150
+ return this . _run ( parameters , options ) ;
151
+ } catch ( e ) {
152
+ throw mapError ( e ) ;
170
153
}
171
- return { rows, done : isDone } as SqliteStepResult ;
172
154
}
173
155
174
- finalize ( ) : void {
175
- const existingIter = this . iterator ;
176
- if ( existingIter != null ) {
177
- existingIter . return ?.( ) ;
178
- }
179
- this . iterator = undefined ;
180
- this . statementDone = false ;
156
+ _run (
157
+ parameters : SqliteParameterBinding ,
158
+ options ?: QueryOptions
159
+ ) : SqliteChanges {
160
+ this . checkTransaction ( options ) ;
161
+
162
+ const statement = this . statement ;
163
+
164
+ statement . safeIntegers ( true ) ;
165
+ const r = statement . run ( sanitizeParameters ( parameters ) ) ;
166
+ return {
167
+ changes : r . changes ,
168
+ lastInsertRowId : r . lastInsertRowid as bigint
169
+ } ;
181
170
}
182
171
183
- reset ( options ?: ResetOptions ) : void {
184
- if ( this . iterator ) {
185
- const iter = this . iterator ;
186
- iter . return ! ( ) ;
187
- this . iterator = undefined ;
188
- }
189
- if ( options ?. clearBindings ) {
190
- this . bindNamed = { } ;
191
- this . bindPositional = [ ] ;
192
- }
193
- this . statementDone = false ;
172
+ finalize ( ) : void {
173
+ // TODO: cancel iterators
194
174
}
195
175
}
196
176
@@ -286,3 +266,25 @@ export class BetterSqliteConnection implements SqliteDriverConnection {
286
266
return ( ) => { } ;
287
267
}
288
268
}
269
+
270
+ function sanitizeParameters (
271
+ parameters : SqliteParameterBinding
272
+ ) : SqliteParameterBinding {
273
+ if ( parameters == null ) {
274
+ return [ ] ;
275
+ } else if ( Array . isArray ( parameters ) ) {
276
+ return parameters ;
277
+ }
278
+ let result : Record < string , SqliteValue > = { } ;
279
+ for ( let key in parameters ) {
280
+ const value = parameters [ key ] ;
281
+ let name = key ;
282
+ const prefix = key [ 0 ] ;
283
+ // better-sqlite doesn't support the explicit prefix - strip it
284
+ if ( prefix == ':' || prefix == '?' || prefix == '$' || prefix == '@' ) {
285
+ name = key . substring ( 1 ) ;
286
+ }
287
+ result [ name ] = value ;
288
+ }
289
+ return result ;
290
+ }
0 commit comments