@velarscript-labs/sqlite 0.2.2 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,15 +4,15 @@
4
4
  "abiVersion": 1,
5
5
  "package": {
6
6
  "name": "@velarscript-labs/sqlite",
7
- "version": "0.2.2"
7
+ "version": "0.3.2"
8
8
  },
9
9
  "target": "node",
10
- "compilerVersion": "0.14.2",
10
+ "compilerVersion": "0.15.0",
11
11
  "sourceEntry": "src/index.vel",
12
12
  "sources": [
13
13
  {
14
14
  "path": "src/index.vel",
15
- "sha256": "e6272cf2f7d0ca88034fcf3deb7db7c9e63158165017167933b79a1887cad390"
15
+ "sha256": "d35ec69e071fdeab0e379a78209c7f075cabf1b9b5148c9ba033a325e9c482b6"
16
16
  }
17
17
  ],
18
18
  "entry": {
@@ -20,9 +20,9 @@
20
20
  "sourceMap": "index.js.map",
21
21
  "interface": "index.veli.json",
22
22
  "sha256": {
23
- "javascript": "271affd5cabaf9b34c49a706e7965d9c3942f2ddffe9e921d7bdc092102ebffb",
24
- "sourceMap": "62cb96e139d5bbe00c042081a0f2c713efe54b54a0ebd030be92db182ea0f59e",
25
- "interface": "31f47fb9c50ce51bcdb1ab035aa2d279687ff4adba06eeab9c463f1f7e683869"
23
+ "javascript": "33086d56ae3878ed41d4c31405ce57e17b8aca65ec3160a9a82ceada7b4b49d7",
24
+ "sourceMap": "d2e01a92404964ef4674985191acf85be17185a1ea75fb2369f5acaf6fb8aecf",
25
+ "interface": "93ed6516f8b9c501b068b4550424e472d536e1e7a9b3451bba536b3c3e736012"
26
26
  }
27
27
  }
28
28
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@velarscript-labs/sqlite",
3
- "version": "0.2.2",
4
- "description": "Bounded asynchronous SQLite capability for VelarScript Node applications.",
3
+ "version": "0.3.2",
4
+ "description": "Injection-resistant bounded SQLite capability for VelarScript Node applications.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "files": [
@@ -17,7 +17,7 @@
17
17
  "test": "velar test"
18
18
  },
19
19
  "engines": {
20
- "node": ">=24"
20
+ "node": ">=24.12"
21
21
  },
22
22
  "repository": {
23
23
  "type": "git",
@@ -34,9 +34,10 @@
34
34
  "entry": "src/index.vel",
35
35
  "artifacts": { "node": "dist/velar-library.json" },
36
36
  "targets": ["node"],
37
- "requires": {"capabilities": ["node"], "language": ">=0.13 <0.15"}
37
+ "requires": {"capabilities": ["node"], "language": ">=0.15 <0.16"}
38
38
  },
39
39
  "dependencies": {
40
- "@velarscript-labs/database": "0.2.2"
40
+ "@velarscript-labs/database": "0.3.2",
41
+ "sqlite3-parser": "0.7.1"
41
42
  }
42
43
  }
package/src/index.vel CHANGED
@@ -1,14 +1,25 @@
1
1
  /// Per-connection bounds. Every operation is serialized through one Worker;
2
2
  /// queueCapacity bounds admitted operations waiting for that connection.
3
- import {DatabaseExecutor} from "@velarscript-labs/database"
3
+ import {DatabaseExecutor, DatabaseStatement, trustedSql} from "@velarscript-labs/database"
4
+
5
+ export enum SqliteJournalMode:
6
+ delete
7
+ truncate
8
+ persist
9
+ memory
10
+ wal
11
+ off
4
12
 
5
13
  export type SqliteOptions:
6
14
  busyTimeoutMilliseconds: number?
7
15
  queueCapacity: number?
8
16
  maxRows: number?
9
17
  maxResultBytes: number?
18
+ statementCacheCapacity: number?
19
+ journalMode: SqliteJournalMode?
20
+ readOnly: bool?
10
21
 
11
- extern js()`
22
+ extern js`
12
23
  import { AsyncLocalStorage } from "node:async_hooks";
13
24
  import { Worker } from "node:worker_threads";
14
25
 
@@ -51,21 +62,31 @@ extern js()`
51
62
 
52
63
  function checkedOptions(value) {
53
64
  if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError("SQLite options must be a record");
54
- const allowed = new Set(["busyTimeoutMilliseconds", "queueCapacity", "maxRows", "maxResultBytes"]);
65
+ const allowed = new Set(["busyTimeoutMilliseconds", "queueCapacity", "maxRows", "maxResultBytes", "statementCacheCapacity", "journalMode", "readOnly"]);
55
66
  for (const key of Object.keys(value)) if (!allowed.has(key)) throw new TypeError("Unknown SQLite option " + key);
67
+ const journalModes = new Set(["delete", "truncate", "persist", "memory", "wal", "off"]);
68
+ const journalMode = value.journalMode == null ? null : value.journalMode;
69
+ if (journalMode !== null && (typeof journalMode !== "string" || !journalModes.has(journalMode))) throw new TypeError("SQLite journalMode must name a supported mode");
70
+ if (value.readOnly != null && typeof value.readOnly !== "boolean") throw new TypeError("SQLite readOnly must be bool");
71
+ if (value.readOnly === true && journalMode !== null) throw new TypeError("SQLite readOnly connections cannot change journalMode");
56
72
  return {
57
73
  busyTimeoutMilliseconds: integer(value.busyTimeoutMilliseconds, 2000, 1, 60000, "SQLite busy timeout"),
58
74
  queueCapacity: integer(value.queueCapacity, 64, 1, 1024, "SQLite queue capacity"),
59
75
  maxRows: integer(value.maxRows, 10000, 1, 1000000, "SQLite row limit"),
60
76
  maxResultBytes: integer(value.maxResultBytes, 64 * 1024 * 1024, 1, 128 * 1024 * 1024, "SQLite result byte limit"),
77
+ statementCacheCapacity: integer(value.statementCacheCapacity, 128, 1, 1024, "SQLite statement cache capacity"),
78
+ journalMode,
79
+ readOnly: value.readOnly === true,
61
80
  };
62
81
  }
63
82
 
64
83
  function workerMain() {
65
84
  const { Buffer } = require("node:buffer");
66
85
  const { parentPort, workerData } = require("node:worker_threads");
67
- const { DatabaseSync } = require("node:sqlite");
86
+ const { DatabaseSync, constants } = require("node:sqlite");
87
+ const { parseStmt } = require("sqlite3-parser");
68
88
  let database = null;
89
+ let statements = null;
69
90
  let closed = false;
70
91
 
71
92
  function failure(message, code, operation, retryable = false) {
@@ -89,13 +110,6 @@ extern js()`
89
110
  };
90
111
  }
91
112
 
92
- function sqlText(value, operation) {
93
- if (typeof value !== "string" || value.length === 0 || value.length > 1024 * 1024 || value.includes("\0")) {
94
- throw failure("SQLite SQL must be non-empty text no longer than 1 MiB", "SQLITE_INPUT", operation);
95
- }
96
- return value;
97
- }
98
-
99
113
  function parameter(value, budget) {
100
114
  if (value === null) return null;
101
115
  if (typeof value === "boolean") return value ? 1 : 0;
@@ -124,6 +138,29 @@ extern js()`
124
138
  return output;
125
139
  }
126
140
 
141
+ function statementParts(rawFragments, rawParameters, operation) {
142
+ if (!Array.isArray(rawFragments) || rawFragments.length !== rawParameters.length + 1 || rawFragments.length > 1000) {
143
+ throw failure("SQLite statements require exactly one more SQL fragment than bound parameters", "SQLITE_INPUT", operation);
144
+ }
145
+ let sourceBytes = 0;
146
+ const fragments = rawFragments.map(fragment => {
147
+ if (typeof fragment !== "string" || fragment.includes("\0") || fragment.includes("?")) {
148
+ throw failure("SQLite SQL fragments must be text without NUL or raw parameter placeholders", "SQLITE_INPUT", operation);
149
+ }
150
+ sourceBytes += Buffer.byteLength(fragment, "utf8");
151
+ return fragment;
152
+ });
153
+ if (sourceBytes === 0 || sourceBytes > 1024 * 1024) throw failure("SQLite SQL must be non-empty text no longer than 1 MiB", "SQLITE_INPUT", operation);
154
+ const bound = parameters(rawParameters);
155
+ const source = fragments.join("?");
156
+ const parsed = parseStmt(source);
157
+ if (parsed.status !== "ok") {
158
+ const message = parsed.errors.length === 0 ? "invalid statement" : parsed.errors[0].message;
159
+ throw failure("SQLite requires exactly one valid statement: " + message, "SQLITE_INPUT", operation);
160
+ }
161
+ return { fragments, bound };
162
+ }
163
+
127
164
  function count(value, label) {
128
165
  const number = typeof value === "bigint" ? Number(value) : value;
129
166
  if (!Number.isSafeInteger(number) || number < 0) throw failure(label + " is outside VelarScript's safe integer range", "SQLITE_INTEGER_RANGE", "result");
@@ -165,30 +202,24 @@ extern js()`
165
202
  return output;
166
203
  }
167
204
 
168
- function statement(sql, operation) {
169
- const prepared = database.prepare(sqlText(sql, operation));
170
- if (typeof prepared.setReadBigInts === "function") prepared.setReadBigInts(true);
171
- return prepared;
172
- }
173
-
174
205
  function execute(message) {
175
- const prepared = statement(message.sql, "execute");
176
- const result = prepared.run(...parameters(message.params));
206
+ const statement = statementParts(message.fragments, message.params, "execute");
207
+ const result = statements.run(statement.fragments, ...statement.bound);
177
208
  return count(result.changes, "SQLite affected row count");
178
209
  }
179
210
 
180
211
  function one(message) {
181
- const prepared = statement(message.sql, "one");
182
- const row = prepared.get(...parameters(message.params));
212
+ const statement = statementParts(message.fragments, message.params, "one");
213
+ const row = statements.get(statement.fragments, ...statement.bound);
183
214
  if (row === undefined) return null;
184
215
  return resultRow(row, { value: 0 });
185
216
  }
186
217
 
187
218
  function all(message) {
188
- const prepared = statement(message.sql, "all");
219
+ const statement = statementParts(message.fragments, message.params, "all");
189
220
  const rows = [];
190
221
  const budget = { value: 0 };
191
- for (const row of prepared.iterate(...parameters(message.params))) {
222
+ for (const row of statements.iterate(statement.fragments, ...statement.bound)) {
192
223
  if (rows.length >= workerData.maxRows) throw failure("SQLite result exceeds the connection row limit", "SQLITE_ROW_LIMIT", "all");
193
224
  rows.push(resultRow(row, budget));
194
225
  }
@@ -227,8 +258,39 @@ extern js()`
227
258
  }
228
259
 
229
260
  try {
230
- database = new DatabaseSync(workerData.path, { timeout: workerData.busyTimeoutMilliseconds });
231
- database.exec("PRAGMA foreign_keys = ON");
261
+ database = new DatabaseSync(workerData.path, {
262
+ timeout: workerData.busyTimeoutMilliseconds,
263
+ readOnly: workerData.readOnly,
264
+ enableForeignKeyConstraints: true,
265
+ enableDoubleQuotedStringLiterals: false,
266
+ allowExtension: false,
267
+ readBigInts: true,
268
+ allowBareNamedParameters: false,
269
+ allowUnknownNamedParameters: false,
270
+ defensive: true,
271
+ limits: {
272
+ length: workerData.maxResultBytes,
273
+ sqlLength: 1024 * 1024,
274
+ column: 2000,
275
+ exprDepth: 1000,
276
+ compoundSelect: 500,
277
+ vdbeOp: 250000,
278
+ functionArg: 1000,
279
+ attach: 0,
280
+ likePatternLength: 50000,
281
+ variableNumber: 999,
282
+ triggerDepth: 100,
283
+ },
284
+ });
285
+ database.enableDefensive(true);
286
+ database.enableLoadExtension(false);
287
+ database.setAuthorizer((actionCode, arg1, arg2) => {
288
+ if (actionCode === constants.SQLITE_ATTACH || actionCode === constants.SQLITE_DETACH) return constants.SQLITE_DENY;
289
+ if (actionCode === constants.SQLITE_FUNCTION && typeof arg2 === "string" && arg2.toLowerCase() === "load_extension") return constants.SQLITE_DENY;
290
+ return constants.SQLITE_OK;
291
+ });
292
+ statements = database.createTagStore(workerData.statementCacheCapacity);
293
+ if (workerData.journalMode !== null) database.exec("PRAGMA journal_mode = " + workerData.journalMode.toUpperCase());
232
294
  parentPort.postMessage({ kind: "ready" });
233
295
  } catch (error) {
234
296
  parentPort.postMessage({ kind: "ready", error: errorData(error, "open") });
@@ -261,13 +323,13 @@ extern js()`
261
323
  state.pending.clear();
262
324
  }
263
325
 
264
- function request(state, operation, sql = "", params = []) {
326
+ function request(state, operation, fragments = [""], params = []) {
265
327
  if (state.closed) return Promise.reject(new NativeSqliteError("SQLite connection is closed", "SQLITE_CLOSED", operation));
266
328
  const id = state.nextId++;
267
329
  return new Promise((resolve, reject) => {
268
330
  state.pending.set(id, { resolve, reject });
269
331
  try {
270
- state.worker.postMessage({ id, operation, sql, params });
332
+ state.worker.postMessage({ id, operation, fragments, params });
271
333
  } catch (error) {
272
334
  state.pending.delete(id);
273
335
  reject(error);
@@ -287,11 +349,11 @@ extern js()`
287
349
  return state;
288
350
  }
289
351
 
290
- function transactionRequest(transaction, operation, sql = "", params = []) {
352
+ function transactionRequest(transaction, operation, fragments = [""], params = []) {
291
353
  const state = transactionStateOf(transaction);
292
354
  if (state.busy) return Promise.reject(new NativeSqliteConcurrencyError("SQLite transaction operations must be awaited one at a time"));
293
355
  state.busy = true;
294
- return request(state.connection, operation, sql, params).finally(() => { state.busy = false; });
356
+ return request(state.connection, operation, fragments, params).finally(() => { state.busy = false; });
295
357
  }
296
358
 
297
359
  function enqueue(state, operation) {
@@ -310,9 +372,9 @@ extern js()`
310
372
  transactionStates.set(this, state);
311
373
  }
312
374
 
313
- execute(sql, params = []) { return transactionRequest(this, "execute", sql, params); }
314
- one(sql, params = []) { return transactionRequest(this, "one", sql, params); }
315
- all(sql, params = []) { return transactionRequest(this, "all", sql, params); }
375
+ execute(fragments, params = []) { return transactionRequest(this, "execute", fragments, params); }
376
+ one(fragments, params = []) { return transactionRequest(this, "one", fragments, params); }
377
+ all(fragments, params = []) { return transactionRequest(this, "all", fragments, params); }
316
378
  }
317
379
 
318
380
  export class NativeSqliteConnection {
@@ -321,19 +383,19 @@ extern js()`
321
383
  connectionStates.set(this, state);
322
384
  }
323
385
 
324
- execute(sql, params = []) {
386
+ execute(fragments, params = []) {
325
387
  const state = stateOf(this);
326
- return enqueue(state, () => request(state, "execute", sql, params));
388
+ return enqueue(state, () => request(state, "execute", fragments, params));
327
389
  }
328
390
 
329
- one(sql, params = []) {
391
+ one(fragments, params = []) {
330
392
  const state = stateOf(this);
331
- return enqueue(state, () => request(state, "one", sql, params));
393
+ return enqueue(state, () => request(state, "one", fragments, params));
332
394
  }
333
395
 
334
- all(sql, params = []) {
396
+ all(fragments, params = []) {
335
397
  const state = stateOf(this);
336
- return enqueue(state, () => request(state, "all", sql, params));
398
+ return enqueue(state, () => request(state, "all", fragments, params));
337
399
  }
338
400
 
339
401
  transaction(operation) {
@@ -448,14 +510,14 @@ extern js()`
448
510
  const message: string
449
511
 
450
512
  export class NativeSqliteTransaction:
451
- async def execute(sql: string, params: List<unknown> = []) -> number
452
- async def one(sql: string, params: List<unknown> = []) -> unknown
453
- async def all(sql: string, params: List<unknown> = []) -> List<unknown>
513
+ async def execute(fragments: readonly List<string>, params: readonly List<unknown> = []) -> number
514
+ async def one(fragments: readonly List<string>, params: readonly List<unknown> = []) -> unknown
515
+ async def all(fragments: readonly List<string>, params: readonly List<unknown> = []) -> List<unknown>
454
516
 
455
517
  export class NativeSqliteConnection:
456
- async def execute(sql: string, params: List<unknown> = []) -> number
457
- async def one(sql: string, params: List<unknown> = []) -> unknown
458
- async def all(sql: string, params: List<unknown> = []) -> List<unknown>
518
+ async def execute(fragments: readonly List<string>, params: readonly List<unknown> = []) -> number
519
+ async def one(fragments: readonly List<string>, params: readonly List<unknown> = []) -> unknown
520
+ async def all(fragments: readonly List<string>, params: readonly List<unknown> = []) -> List<unknown>
459
521
  async def transaction(operation: (NativeSqliteTransaction) -> Promise<null>) -> null
460
522
  async def close() -> null
461
523
 
@@ -475,160 +537,156 @@ export class SqliteError extends Error:
475
537
  self.retryable = retryable
476
538
 
477
539
  export class SqliteBackpressureError extends SqliteError:
478
- constructor(message: string = "SQLite queue is full"):
479
- super(message, "SQLITE_BACKPRESSURE", "queue", true)
540
+ constructor(message: string = "SQLite queue is full"): super(message, "SQLITE_BACKPRESSURE", "queue", true)
480
541
 
481
542
  export class SqliteConcurrencyError extends SqliteError:
482
543
  constructor(message: string = "SQLite connection cannot be used from its transaction callback"):
483
544
  super(message, "SQLITE_CONCURRENCY", "transaction", true)
484
545
 
485
546
  def sqliteFailure(error: NativeSqliteError) -> SqliteError:
486
- if error is NativeSqliteBackpressureError:
487
- return SqliteBackpressureError(error.message)
488
- if error is NativeSqliteConcurrencyError:
489
- return SqliteConcurrencyError(error.message)
490
- if error is NativeSqliteError:
491
- return SqliteError(error.message, error.sqliteCode, error.operation, error.retryable)
547
+ if error is NativeSqliteBackpressureError: return SqliteBackpressureError(error.message)
548
+ if error is NativeSqliteConcurrencyError: return SqliteConcurrencyError(error.message)
549
+ if error is NativeSqliteError: return SqliteError(error.message, error.sqliteCode, error.operation, error.retryable)
492
550
  return SqliteError(error.message)
493
551
 
552
+ /// Produces a SQLite literal only for grammar positions where bound parameters
553
+ /// are not legal, such as schema defaults and PRAGMA assignments. Ordinary
554
+ /// query values must still use sqlParameter/sqlTuple/sqlRows.
555
+ export def sqliteLiteral(value: unknown) -> DatabaseStatement:
556
+ if value == null: return trustedSql("NULL")
557
+ if value is bool: return trustedSql(value ? "1" : "0")
558
+ if value is string: return trustedSql("'" + value.replaceAll("'", "''") + "'")
559
+ if value is number:
560
+ assert value.isFinite() else "SQLite numeric literals must be finite"
561
+ assert not value.isInteger() or (value >= -9007199254740991 and value <= 9007199254740991) else "SQLite integer literals must be safe integers"
562
+ return trustedSql(str(value))
563
+ throw Error("SQLite literals support only null, bool, string, and finite number")
564
+
565
+ /// Quotes one SQLite identifier as a single name. Qualified names must compose
566
+ /// separately quoted identifiers around trusted punctuation.
567
+ export def sqliteIdentifier(value: string) -> DatabaseStatement:
568
+ assert not value.isBlank() and value.size <= 255 and not value.has(Text.fromCodePoint(0)) else "SQLite identifiers must be non-blank text with no NUL and no longer than 255 characters"
569
+ return trustedSql(`"` + value.replaceAll(`"`, `""`) + `"`)
570
+
494
571
  export class SqliteTransaction:
495
- constructor(private const native: NativeSqliteTransaction):
496
- pass
572
+ constructor(private const native: NativeSqliteTransaction): pass
497
573
 
498
- async def execute(sql: string, params: List<unknown> = []) -> number:
499
- try:
500
- return await self.native.execute(sql, params)
574
+ async def execute(statement: DatabaseStatement) -> number:
575
+ const data = statement.data()
576
+ try: return await self.native.execute(data.fragments, data.parameters)
501
577
  catch error:
502
- if error is NativeSqliteError:
503
- throw sqliteFailure(error)
578
+ if error is NativeSqliteError: throw sqliteFailure(error)
504
579
  throw SqliteError(error.message)
505
580
 
506
581
  /// Projects this transaction into the engine-neutral functional operation layer.
507
582
  def executor() -> DatabaseExecutor:
508
- async def executeStatement(text: string, parameters: List<unknown>) -> number:
509
- return await self.execute(text, parameters)
583
+ async def executeStatement(statement: DatabaseStatement) -> number: return await self.execute(statement)
510
584
 
511
- async def oneRow(text: string, parameters: List<unknown>) -> unknown:
512
- try:
513
- return await self.native.one(text, parameters)
585
+ async def oneRow(statement: DatabaseStatement) -> unknown:
586
+ const data = statement.data()
587
+ try: return await self.native.one(data.fragments, data.parameters)
514
588
  catch error:
515
- if error is NativeSqliteError:
516
- throw sqliteFailure(error)
589
+ if error is NativeSqliteError: throw sqliteFailure(error)
517
590
  throw SqliteError(error.message)
518
591
 
519
- async def allRows(text: string, parameters: List<unknown>) -> List<unknown>:
520
- try:
521
- return await self.native.all(text, parameters)
592
+ async def allRows(statement: DatabaseStatement) -> List<unknown>:
593
+ const data = statement.data()
594
+ try: return await self.native.all(data.fragments, data.parameters)
522
595
  catch error:
523
- if error is NativeSqliteError:
524
- throw sqliteFailure(error)
596
+ if error is NativeSqliteError: throw sqliteFailure(error)
525
597
  throw SqliteError(error.message)
526
598
 
527
599
  return {execute: executeStatement, one: oneRow, all: allRows}
528
600
 
529
- async def one<T>(sql: string, RowType: Type<T>, params: List<unknown> = []) -> T?:
601
+ async def one<T>(statement: DatabaseStatement, RowType: Type<T>) -> T?:
602
+ const data = statement.data()
530
603
  try:
531
- const row = await self.native.one(sql, params)
532
- if row == null:
533
- return null
604
+ const row = await self.native.one(data.fragments, data.parameters)
605
+ if row == null: return null
534
606
  return RowType.parse(row)
535
607
  catch error:
536
- if error is NativeSqliteError:
537
- throw sqliteFailure(error)
608
+ if error is NativeSqliteError: throw sqliteFailure(error)
538
609
  throw SqliteError(error.message)
539
610
 
540
- async def all<T>(sql: string, RowType: Type<T>, params: List<unknown> = []) -> List<T>:
611
+ async def all<T>(statement: DatabaseStatement, RowType: Type<T>) -> List<T>:
612
+ const data = statement.data()
541
613
  try:
542
- const rows = await self.native.all(sql, params)
614
+ const rows = await self.native.all(data.fragments, data.parameters)
543
615
  return rows.map(row => RowType.parse(row))
544
616
  catch error:
545
- if error is NativeSqliteError:
546
- throw sqliteFailure(error)
617
+ if error is NativeSqliteError: throw sqliteFailure(error)
547
618
  throw SqliteError(error.message)
548
619
 
549
620
  export class SqliteConnection:
550
- constructor(private const native: NativeSqliteConnection):
551
- pass
621
+ constructor(private const native: NativeSqliteConnection): pass
552
622
 
553
- async def execute(sql: string, params: List<unknown> = []) -> number:
554
- try:
555
- return await self.native.execute(sql, params)
623
+ async def execute(statement: DatabaseStatement) -> number:
624
+ const data = statement.data()
625
+ try: return await self.native.execute(data.fragments, data.parameters)
556
626
  catch error:
557
- if error is NativeSqliteError:
558
- throw sqliteFailure(error)
627
+ if error is NativeSqliteError: throw sqliteFailure(error)
559
628
  throw SqliteError(error.message)
560
629
 
561
630
  /// Projects this connection into the engine-neutral functional operation layer.
562
631
  def executor() -> DatabaseExecutor:
563
- async def executeStatement(text: string, parameters: List<unknown>) -> number:
564
- return await self.execute(text, parameters)
632
+ async def executeStatement(statement: DatabaseStatement) -> number: return await self.execute(statement)
565
633
 
566
- async def oneRow(text: string, parameters: List<unknown>) -> unknown:
567
- try:
568
- return await self.native.one(text, parameters)
634
+ async def oneRow(statement: DatabaseStatement) -> unknown:
635
+ const data = statement.data()
636
+ try: return await self.native.one(data.fragments, data.parameters)
569
637
  catch error:
570
- if error is NativeSqliteError:
571
- throw sqliteFailure(error)
638
+ if error is NativeSqliteError: throw sqliteFailure(error)
572
639
  throw SqliteError(error.message)
573
640
 
574
- async def allRows(text: string, parameters: List<unknown>) -> List<unknown>:
575
- try:
576
- return await self.native.all(text, parameters)
641
+ async def allRows(statement: DatabaseStatement) -> List<unknown>:
642
+ const data = statement.data()
643
+ try: return await self.native.all(data.fragments, data.parameters)
577
644
  catch error:
578
- if error is NativeSqliteError:
579
- throw sqliteFailure(error)
645
+ if error is NativeSqliteError: throw sqliteFailure(error)
580
646
  throw SqliteError(error.message)
581
647
 
582
648
  return {execute: executeStatement, one: oneRow, all: allRows}
583
649
 
584
- async def one<T>(sql: string, RowType: Type<T>, params: List<unknown> = []) -> T?:
650
+ async def one<T>(statement: DatabaseStatement, RowType: Type<T>) -> T?:
651
+ const data = statement.data()
585
652
  try:
586
- const row = await self.native.one(sql, params)
587
- if row == null:
588
- return null
653
+ const row = await self.native.one(data.fragments, data.parameters)
654
+ if row == null: return null
589
655
  return RowType.parse(row)
590
656
  catch error:
591
- if error is NativeSqliteError:
592
- throw sqliteFailure(error)
657
+ if error is NativeSqliteError: throw sqliteFailure(error)
593
658
  throw SqliteError(error.message)
594
659
 
595
- async def all<T>(sql: string, RowType: Type<T>, params: List<unknown> = []) -> List<T>:
660
+ async def all<T>(statement: DatabaseStatement, RowType: Type<T>) -> List<T>:
661
+ const data = statement.data()
596
662
  try:
597
- const rows = await self.native.all(sql, params)
663
+ const rows = await self.native.all(data.fragments, data.parameters)
598
664
  return rows.map(row => RowType.parse(row))
599
665
  catch error:
600
- if error is NativeSqliteError:
601
- throw sqliteFailure(error)
666
+ if error is NativeSqliteError: throw sqliteFailure(error)
602
667
  throw SqliteError(error.message)
603
668
 
604
669
  async def transaction<T>(operation: (SqliteTransaction) -> Promise<T>) -> T:
605
670
  const results: List<T> = []
606
- async def run<T>(native: NativeSqliteTransaction):
607
- results.append(await operation(SqliteTransaction(native)))
671
+ async def run<T>(native: NativeSqliteTransaction): results.append(await operation(SqliteTransaction(native)))
608
672
  try:
609
673
  await self.native.transaction(run)
610
674
  assert results.size == 1 else "SQLite transaction did not return exactly one result"
611
675
  return results[0]
612
676
  catch error:
613
- if error is NativeSqliteError:
614
- throw sqliteFailure(error)
677
+ if error is NativeSqliteError: throw sqliteFailure(error)
615
678
  throw error
616
679
 
617
680
  async def close():
618
- try:
619
- await self.native.close()
681
+ try: await self.native.close()
620
682
  catch error:
621
- if error is NativeSqliteError:
622
- throw sqliteFailure(error)
683
+ if error is NativeSqliteError: throw sqliteFailure(error)
623
684
  throw SqliteError(error.message)
624
685
 
625
- @dispose:
626
- await self.close()
686
+ @dispose: await self.close()
627
687
 
628
688
  export async def openSqlite(path: string, options: SqliteOptions = {}) -> SqliteConnection:
629
- try:
630
- return SqliteConnection(await openNativeSqlite(path, options))
689
+ try: return SqliteConnection(await openNativeSqlite(path, options))
631
690
  catch error:
632
- if error is NativeSqliteError:
633
- throw sqliteFailure(error)
691
+ if error is NativeSqliteError: throw sqliteFailure(error)
634
692
  throw SqliteError(error.message)