Skip to content

Commit 0046158

Browse files
committed
feat: allow changing int64, parseJson at statement level
1 parent ceffff6 commit 0046158

File tree

3 files changed

+107
-23
lines changed

3 files changed

+107
-23
lines changed

doc.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ For more details on binding parameters, see
174174
- `readonly: boolean` - Whether the statement is read-only.
175175
- `bindParameterCount: number` - The number of parameters the statement expects.
176176

177+
You can use `enableInt64`, `enableParseJson`, `disableInt64`,
178+
`disableParseJson`, `defaultInt64`, `defaultParseJson` at statement level to
179+
override the behavior just for this statement and not whole database. Default
180+
falls back to database, not the default value for these options.
181+
177182
## Executing Statement
178183

179184
To execute a statement, use the `run()` method. This method will execute the
@@ -397,7 +402,14 @@ When retrieving rows, the types are mapped back to JavaScript types:
397402
| `TEXT` with JSON subtype | `object` (`JSON.parse()`) |
398403
| `BLOB` | `Uint8Array` |
399404

400-
Note: We only support `Uint8Array` for the `BLOB` type as V8 Fast API will
405+
Note 1: The `int64` option allows you to return `BigInt` for integers bigger
406+
than max safe integer in JavaScript. It is disabled by default, and precision
407+
may not be accurate for bigger numbers. When enabled, the library can return
408+
both `number | bigint`, but when disabled (default), it will only return
409+
`number`. In the former case, `bigint` is returned ONLY if its too big to fit in
410+
a JavaScript Number.
411+
412+
Note 2: We only support `Uint8Array` for the `BLOB` type as V8 Fast API will
401413
optimize for it instead of other arrays like `Uint16Array`. And it is also to
402414
stay consistent: we only support passing `Uint8Array` and we consistently return
403415
`Uint8Array` when we return a `BLOB` to JS. It is easy to support passing all
@@ -414,3 +426,6 @@ const u8 = new Uint8Array(f32.buffer); // no copy, can pass this
414426
const u8FromSqlite = new Uint8Array(4);
415427
const f32FromSqlite = new Float32Array(u8FromSqlite.buffer); // safely convert back when retrieved from sqlite, no copy
416428
```
429+
430+
Note 3: The `parseJson` option allows you to disable JSON parsing which is
431+
enabled by default.

src/statement.ts

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,39 @@ export class Statement<TStatement extends object = Record<string, any>> {
196196
return this.#bindParameterCount;
197197
}
198198

199+
int64?: boolean;
200+
parseJson?: boolean;
201+
202+
enableInt64(): this {
203+
this.int64 = true;
204+
return this;
205+
}
206+
207+
enableParseJson(): this {
208+
this.parseJson = true;
209+
return this;
210+
}
211+
212+
disableInt64(): this {
213+
this.int64 = false;
214+
return this;
215+
}
216+
217+
disableParseJson(): this {
218+
this.parseJson = false;
219+
return this;
220+
}
221+
222+
defaultInt64(): this {
223+
this.int64 = undefined;
224+
return this;
225+
}
226+
227+
defaultParseJson(): this {
228+
this.parseJson = undefined;
229+
return this;
230+
}
231+
199232
constructor(public db: Database, sql: string) {
200233
const pHandle = new BigUint64Array(1);
201234
unwrap(
@@ -423,10 +456,10 @@ export class Statement<TStatement extends object = Record<string, any>> {
423456
const getRowArray = new Function(
424457
"getColumn",
425458
`
426-
return function(h) {
459+
return function(h, int64, parseJson) {
427460
return [${
428461
Array.from({ length: columnCount }).map((_, i) =>
429-
`getColumn(h, ${i}, ${this.db.int64}, ${this.db.parseJson})`
462+
`getColumn(h, ${i}, int64, parseJson)`
430463
)
431464
.join(", ")
432465
}];
@@ -435,7 +468,7 @@ export class Statement<TStatement extends object = Record<string, any>> {
435468
)(getColumn);
436469
let status = sqlite3_step(handle);
437470
while (status === SQLITE3_ROW) {
438-
result.push(getRowArray(handle));
471+
result.push(getRowArray(handle, this.int64 ?? this.db.int64, this.parseJson ?? this.db.parseJson));
439472
status = sqlite3_step(handle);
440473
}
441474
if (status !== SQLITE3_DONE) {
@@ -456,10 +489,10 @@ export class Statement<TStatement extends object = Record<string, any>> {
456489
const getRowArray = new Function(
457490
"getColumn",
458491
`
459-
return function(h) {
492+
return function(h, int64, parseJson) {
460493
return [${
461494
Array.from({ length: columnCount }).map((_, i) =>
462-
`getColumn(h, ${i}, ${this.db.int64}, ${this.db.parseJson})`
495+
`getColumn(h, ${i}, int64, parseJson)`
463496
)
464497
.join(", ")
465498
}];
@@ -468,7 +501,7 @@ export class Statement<TStatement extends object = Record<string, any>> {
468501
)(getColumn);
469502
let status = sqlite3_step(handle);
470503
while (status === SQLITE3_ROW) {
471-
result.push(getRowArray(handle));
504+
result.push(getRowArray(handle, this.int64 ?? this.db.int64, this.parseJson ?? this.db.parseJson));
472505
status = sqlite3_step(handle);
473506
}
474507
if (!this.#hasNoArgs && !this.#bound && params.length) {
@@ -481,19 +514,19 @@ export class Statement<TStatement extends object = Record<string, any>> {
481514
return result as T[];
482515
}
483516

484-
#rowObjectFn: ((h: Deno.PointerValue) => any) | undefined;
517+
#rowObjectFn: ((h: Deno.PointerValue, int64: boolean, parseJson: boolean) => any) | undefined;
485518

486-
getRowObject(): (h: Deno.PointerValue) => any {
519+
getRowObject(): (h: Deno.PointerValue, int64: boolean, parseJson: boolean) => any {
487520
if (!this.#rowObjectFn || !this.#unsafeConcurrency) {
488521
const columnNames = this.columnNames();
489522
const getRowObject = new Function(
490523
"getColumn",
491524
`
492-
return function(h) {
525+
return function(h, int64, parseJson) {
493526
return {
494527
${
495528
columnNames.map((name, i) =>
496-
`"${name}": getColumn(h, ${i}, ${this.db.int64}, ${this.db.parseJson})`
529+
`"${name}": getColumn(h, ${i}, int64, parseJson)`
497530
).join(",\n")
498531
}
499532
};
@@ -507,12 +540,14 @@ export class Statement<TStatement extends object = Record<string, any>> {
507540

508541
#allNoArgs<T extends object>(): T[] {
509542
const handle = this.#handle;
543+
const int64 = this.int64 ?? this.db.int64;
544+
const parseJson = this.parseJson ?? this.db.parseJson;
510545
this.#begin();
511546
const getRowObject = this.getRowObject();
512547
const result: T[] = [];
513548
let status = sqlite3_step(handle);
514549
while (status === SQLITE3_ROW) {
515-
result.push(getRowObject(handle));
550+
result.push(getRowObject(handle, int64, parseJson));
516551
status = sqlite3_step(handle);
517552
}
518553
if (status !== SQLITE3_DONE) {
@@ -526,13 +561,15 @@ export class Statement<TStatement extends object = Record<string, any>> {
526561
...params: RestBindParameters
527562
): T[] {
528563
const handle = this.#handle;
564+
const int64 = this.int64 ?? this.db.int64;
565+
const parseJson = this.parseJson ?? this.db.parseJson;
529566
this.#begin();
530567
this.#bindAll(params);
531568
const getRowObject = this.getRowObject();
532569
const result: T[] = [];
533570
let status = sqlite3_step(handle);
534571
while (status === SQLITE3_ROW) {
535-
result.push(getRowObject(handle));
572+
result.push(getRowObject(handle, int64, parseJson));
536573
status = sqlite3_step(handle);
537574
}
538575
if (!this.#hasNoArgs && !this.#bound && params.length) {
@@ -550,8 +587,8 @@ export class Statement<TStatement extends object = Record<string, any>> {
550587
...params: RestBindParameters
551588
): T | undefined {
552589
const handle = this.#handle;
553-
const int64 = this.db.int64;
554-
const parseJson = this.db.parseJson;
590+
const int64 = this.int64 ?? this.db.int64;
591+
const parseJson = this.parseJson ?? this.db.parseJson;
555592
const arr = new Array(sqlite3_column_count(handle));
556593
sqlite3_reset(handle);
557594
if (!this.#hasNoArgs && !this.#bound) {
@@ -583,8 +620,8 @@ export class Statement<TStatement extends object = Record<string, any>> {
583620

584621
#valueNoArgs<T extends Array<unknown>>(): T | undefined {
585622
const handle = this.#handle;
586-
const int64 = this.db.int64;
587-
const parseJson = this.db.parseJson;
623+
const int64 = this.int64 ?? this.db.int64;
624+
const parseJson = this.parseJson ?? this.db.parseJson;
588625
const cc = sqlite3_column_count(handle);
589626
const arr = new Array(cc);
590627
sqlite3_reset(handle);
@@ -626,8 +663,8 @@ export class Statement<TStatement extends object = Record<string, any>> {
626663
...params: RestBindParameters
627664
): T | undefined {
628665
const handle = this.#handle;
629-
const int64 = this.db.int64;
630-
const parseJson = this.db.parseJson;
666+
const int64 = this.int64 ?? this.db.int64;
667+
const parseJson = this.parseJson ?? this.db.parseJson;
631668
const columnNames = this.columnNames();
632669

633670
const row: Record<string, unknown> = {};
@@ -661,8 +698,8 @@ export class Statement<TStatement extends object = Record<string, any>> {
661698

662699
#getNoArgs<T extends object>(): T | undefined {
663700
const handle = this.#handle;
664-
const int64 = this.db.int64;
665-
const parseJson = this.db.parseJson;
701+
const int64 = this.int64 ?? this.db.int64;
702+
const parseJson = this.parseJson ?? this.db.parseJson;
666703
const columnNames = this.columnNames();
667704
const row: Record<string, unknown> = this.#rowObject;
668705
sqlite3_reset(handle);
@@ -696,12 +733,14 @@ export class Statement<TStatement extends object = Record<string, any>> {
696733

697734
/** Iterate over resultant rows from query. */
698735
*iter(...params: RestBindParameters): IterableIterator<any> {
699-
this.#begin();
736+
this.#begin();
700737
this.#bindAll(params);
701738
const getRowObject = this.getRowObject();
739+
const int64 = this.int64 ?? this.db.int64;
740+
const parseJson = this.parseJson ?? this.db.parseJson;
702741
let status = sqlite3_step(this.#handle);
703742
while (status === SQLITE3_ROW) {
704-
yield getRowObject(this.#handle);
743+
yield getRowObject(this.#handle, int64, parseJson);
705744
status = sqlite3_step(this.#handle);
706745
}
707746
if (status !== SQLITE3_DONE) {

test/test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,19 @@ Deno.test("sqlite", async (t) => {
237237
assertEquals(row[2], '{"no_subtype": true}');
238238
});
239239

240+
await t.step("query json (disableParseJson at statement level)", () => {
241+
const row = db
242+
.prepare(
243+
"select json('[1,2,3]'), json_object('name', 'alex'), '{\"no_subtype\": true}'",
244+
)
245+
.disableParseJson()
246+
.values<[string, string, string]>()[0];
247+
248+
assertEquals(row[0], "[1,2,3]");
249+
assertEquals(row[1], '{"name":"alex"}');
250+
assertEquals(row[2], '{"no_subtype": true}');
251+
});
252+
240253
await t.step("query with string param", () => {
241254
const row = db.prepare(
242255
"select * from test where text = ?",
@@ -341,6 +354,23 @@ Deno.test("sqlite", async (t) => {
341354
assertEquals(int, value);
342355
});
343356

357+
await t.step("max 64-bit signed int (disableInt64)", () => {
358+
const value = 0x7fffffffffffffffn;
359+
db.exec(
360+
`insert into test (integer, text, double, blob, nullable)
361+
values (?, ?, ?, ?, ?)`,
362+
value,
363+
"bigint3",
364+
0,
365+
new Uint8Array(1),
366+
null,
367+
);
368+
const [int] = db.prepare(
369+
"select integer from test where text = ?",
370+
).disableInt64().values<[bigint]>("bigint3")[0];
371+
assertEquals(int as unknown, -1);
372+
});
373+
344374
await t.step("nan value", () => {
345375
db.exec(
346376
`insert into test (integer, text, double, blob, nullable)

0 commit comments

Comments
 (0)