Skip to content

Commit 6994452

Browse files
committed
Update better-sqlite3-driver.
1 parent d03f87e commit 6994452

File tree

1 file changed

+114
-112
lines changed

1 file changed

+114
-112
lines changed
Lines changed: 114 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import {
22
PrepareOptions,
3-
ResetOptions,
3+
QueryOptions,
4+
SqliteArrayRow,
45
SqliteChanges,
56
SqliteDriverConnection,
67
SqliteDriverStatement,
8+
SqliteObjectRow,
79
SqliteParameterBinding,
8-
SqliteStepResult,
910
SqliteValue,
10-
StepOptions,
11+
StreamQueryOptions,
1112
UpdateListener
1213
} from '@sqlite-js/driver';
1314
import type * as bsqlite from 'better-sqlite3';
@@ -23,19 +24,13 @@ interface InternalStatement extends SqliteDriverStatement {
2324

2425
class BetterSqlitePreparedStatement implements InternalStatement {
2526
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;
3127

3228
readonly persisted: boolean;
3329

3430
[Symbol.dispose]: () => void = undefined as any;
3531

3632
constructor(statement: bsqlite.Statement, options: PrepareOptions) {
3733
this.statement = statement;
38-
this.options = options;
3934
this.persisted = options.autoFinalize ?? false;
4035

4136
if (typeof Symbol.dispose != 'undefined') {
@@ -57,140 +52,125 @@ class BetterSqlitePreparedStatement implements InternalStatement {
5752
}
5853
}
5954

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');
8259
}
8360
}
8461
}
8562

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[]> {
8782
try {
88-
return this.stepSync(n, options);
83+
return this._all(parameters, options, false) as SqliteObjectRow[];
8984
} catch (e) {
9085
throw mapError(e);
9186
}
9287
}
9388

94-
async run(options?: StepOptions): Promise<SqliteChanges> {
89+
async allArray(
90+
parameters?: SqliteParameterBinding,
91+
options?: QueryOptions
92+
): Promise<SqliteArrayRow[]> {
9593
try {
96-
return this.runSync(options);
94+
return this._all(parameters, options, true) as SqliteArrayRow[];
9795
} catch (e) {
9896
throw mapError(e);
9997
}
10098
}
10199

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);
108106

109107
const statement = this.statement;
110-
this.reset();
111108

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+
}
125120
}
126121
}
127122

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);
134131
}
132+
}
135133

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);
140142
}
143+
}
141144

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);
170153
}
171-
return { rows, done: isDone } as SqliteStepResult;
172154
}
173155

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+
};
181170
}
182171

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
194174
}
195175
}
196176

@@ -286,3 +266,25 @@ export class BetterSqliteConnection implements SqliteDriverConnection {
286266
return () => {};
287267
}
288268
}
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

Comments
 (0)