@gjsify/sqlite 0.3.13 → 0.3.14
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.
- package/lib/esm/constants.js +49 -93
- package/lib/esm/data-model-reader.js +69 -73
- package/lib/esm/database-sync.js +374 -395
- package/lib/esm/errors.js +63 -69
- package/lib/esm/index.js +4 -7
- package/lib/esm/param-binding.js +103 -105
- package/lib/esm/statement-sync.js +291 -283
- package/package.json +6 -6
package/lib/esm/database-sync.js
CHANGED
|
@@ -1,403 +1,382 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
ConstructCallRequiredError,
|
|
4
|
-
InvalidArgTypeError,
|
|
5
|
-
InvalidStateError,
|
|
6
|
-
InvalidUrlSchemeError,
|
|
7
|
-
SqliteError
|
|
8
|
-
} from "./errors.js";
|
|
1
|
+
import { ConstructCallRequiredError, InvalidArgTypeError, InvalidStateError, InvalidUrlSchemeError, SqliteError } from "./errors.js";
|
|
9
2
|
import { StatementSync } from "./statement-sync.js";
|
|
10
|
-
|
|
3
|
+
import Gda from "@girs/gda-6.0";
|
|
4
|
+
|
|
5
|
+
//#region src/database-sync.ts
|
|
6
|
+
const sqliteTypeSymbol = Symbol.for("sqlite-type");
|
|
11
7
|
function parsePath(path) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return new TextDecoder().decode(path);
|
|
41
|
-
}
|
|
42
|
-
throw new InvalidArgTypeError(
|
|
43
|
-
'The "path" argument must be a string, Uint8Array, or URL without null bytes.'
|
|
44
|
-
);
|
|
8
|
+
if (typeof path === "string") {
|
|
9
|
+
if (path.includes("\0")) {
|
|
10
|
+
throw new InvalidArgTypeError("The \"path\" argument must be a string, Uint8Array, or URL without null bytes.");
|
|
11
|
+
}
|
|
12
|
+
return path;
|
|
13
|
+
}
|
|
14
|
+
if (path instanceof URL) {
|
|
15
|
+
if (path.protocol !== "file:") {
|
|
16
|
+
throw new InvalidUrlSchemeError();
|
|
17
|
+
}
|
|
18
|
+
const filePath = path.pathname;
|
|
19
|
+
if (filePath.includes("\0")) {
|
|
20
|
+
throw new InvalidArgTypeError("The \"path\" argument must be a string, Uint8Array, or URL without null bytes.");
|
|
21
|
+
}
|
|
22
|
+
return filePath;
|
|
23
|
+
}
|
|
24
|
+
if (path instanceof Uint8Array) {
|
|
25
|
+
for (let i = 0; i < path.length; i++) {
|
|
26
|
+
if (path[i] === 0) {
|
|
27
|
+
throw new InvalidArgTypeError("The \"path\" argument must be a string, Uint8Array, or URL without null bytes.");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return new TextDecoder().decode(path);
|
|
31
|
+
}
|
|
32
|
+
throw new InvalidArgTypeError("The \"path\" argument must be a string, Uint8Array, or URL without null bytes.");
|
|
45
33
|
}
|
|
46
34
|
function validateOptions(options) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
35
|
+
if (options === undefined) return {};
|
|
36
|
+
if (options === null || typeof options !== "object") {
|
|
37
|
+
throw new InvalidArgTypeError("The \"options\" argument must be an object.");
|
|
38
|
+
}
|
|
39
|
+
const opts = options;
|
|
40
|
+
const result = {};
|
|
41
|
+
if (opts.open !== undefined) {
|
|
42
|
+
if (typeof opts.open !== "boolean") {
|
|
43
|
+
throw new InvalidArgTypeError("The \"options.open\" argument must be a boolean.");
|
|
44
|
+
}
|
|
45
|
+
result.open = opts.open;
|
|
46
|
+
}
|
|
47
|
+
if (opts.readOnly !== undefined) {
|
|
48
|
+
if (typeof opts.readOnly !== "boolean") {
|
|
49
|
+
throw new InvalidArgTypeError("The \"options.readOnly\" argument must be a boolean.");
|
|
50
|
+
}
|
|
51
|
+
result.readOnly = opts.readOnly;
|
|
52
|
+
}
|
|
53
|
+
if (opts.timeout !== undefined) {
|
|
54
|
+
if (typeof opts.timeout !== "number" || !Number.isInteger(opts.timeout)) {
|
|
55
|
+
throw new InvalidArgTypeError("The \"options.timeout\" argument must be an integer.");
|
|
56
|
+
}
|
|
57
|
+
result.timeout = opts.timeout;
|
|
58
|
+
}
|
|
59
|
+
if (opts.enableForeignKeyConstraints !== undefined) {
|
|
60
|
+
if (typeof opts.enableForeignKeyConstraints !== "boolean") {
|
|
61
|
+
throw new InvalidArgTypeError("The \"options.enableForeignKeyConstraints\" argument must be a boolean.");
|
|
62
|
+
}
|
|
63
|
+
result.enableForeignKeyConstraints = opts.enableForeignKeyConstraints;
|
|
64
|
+
}
|
|
65
|
+
if (opts.enableDoubleQuotedStringLiterals !== undefined) {
|
|
66
|
+
if (typeof opts.enableDoubleQuotedStringLiterals !== "boolean") {
|
|
67
|
+
throw new InvalidArgTypeError("The \"options.enableDoubleQuotedStringLiterals\" argument must be a boolean.");
|
|
68
|
+
}
|
|
69
|
+
result.enableDoubleQuotedStringLiterals = opts.enableDoubleQuotedStringLiterals;
|
|
70
|
+
}
|
|
71
|
+
if (opts.readBigInts !== undefined) {
|
|
72
|
+
if (typeof opts.readBigInts !== "boolean") {
|
|
73
|
+
throw new InvalidArgTypeError("The \"options.readBigInts\" argument must be a boolean.");
|
|
74
|
+
}
|
|
75
|
+
result.readBigInts = opts.readBigInts;
|
|
76
|
+
}
|
|
77
|
+
if (opts.returnArrays !== undefined) {
|
|
78
|
+
if (typeof opts.returnArrays !== "boolean") {
|
|
79
|
+
throw new InvalidArgTypeError("The \"options.returnArrays\" argument must be a boolean.");
|
|
80
|
+
}
|
|
81
|
+
result.returnArrays = opts.returnArrays;
|
|
82
|
+
}
|
|
83
|
+
if (opts.allowBareNamedParameters !== undefined) {
|
|
84
|
+
if (typeof opts.allowBareNamedParameters !== "boolean") {
|
|
85
|
+
throw new InvalidArgTypeError("The \"options.allowBareNamedParameters\" argument must be a boolean.");
|
|
86
|
+
}
|
|
87
|
+
result.allowBareNamedParameters = opts.allowBareNamedParameters;
|
|
88
|
+
}
|
|
89
|
+
if (opts.allowUnknownNamedParameters !== undefined) {
|
|
90
|
+
if (typeof opts.allowUnknownNamedParameters !== "boolean") {
|
|
91
|
+
throw new InvalidArgTypeError("The \"options.allowUnknownNamedParameters\" argument must be a boolean.");
|
|
92
|
+
}
|
|
93
|
+
result.allowUnknownNamedParameters = opts.allowUnknownNamedParameters;
|
|
94
|
+
}
|
|
95
|
+
if (opts.defensive !== undefined) {
|
|
96
|
+
if (typeof opts.defensive !== "boolean") {
|
|
97
|
+
throw new InvalidArgTypeError("The \"options.defensive\" argument must be a boolean.");
|
|
98
|
+
}
|
|
99
|
+
result.defensive = opts.defensive;
|
|
100
|
+
}
|
|
101
|
+
if (opts.allowExtension !== undefined) {
|
|
102
|
+
if (typeof opts.allowExtension !== "boolean") {
|
|
103
|
+
throw new InvalidArgTypeError("The \"options.allowExtension\" argument must be a boolean.");
|
|
104
|
+
}
|
|
105
|
+
result.allowExtension = opts.allowExtension;
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
120
108
|
}
|
|
121
109
|
function convertParameterSyntax(sql) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
constructor(path, options) {
|
|
181
|
-
if (!new.target) {
|
|
182
|
-
throw new ConstructCallRequiredError("DatabaseSync");
|
|
183
|
-
}
|
|
184
|
-
this.#path = parsePath(path);
|
|
185
|
-
this.#options = validateOptions(options);
|
|
186
|
-
this.#isMemory = this.#path === ":memory:";
|
|
187
|
-
const shouldOpen = this.#options.open !== false;
|
|
188
|
-
if (shouldOpen) {
|
|
189
|
-
this.open();
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
get [sqliteTypeSymbol]() {
|
|
193
|
-
return "node:sqlite";
|
|
194
|
-
}
|
|
195
|
-
get isOpen() {
|
|
196
|
-
return this.#connection !== null && this.#connection.is_opened();
|
|
197
|
-
}
|
|
198
|
-
get isTransaction() {
|
|
199
|
-
this.#ensureOpen();
|
|
200
|
-
return this.#inTransaction;
|
|
201
|
-
}
|
|
202
|
-
open() {
|
|
203
|
-
if (this.isOpen) {
|
|
204
|
-
throw new InvalidStateError("database is already open");
|
|
205
|
-
}
|
|
206
|
-
try {
|
|
207
|
-
if (this.#isMemory) {
|
|
208
|
-
this.#connection = Gda.Connection.new_from_string(
|
|
209
|
-
"SQLite",
|
|
210
|
-
"DB_DIR=;DB_NAME=:memory:",
|
|
211
|
-
null,
|
|
212
|
-
Gda.ConnectionOptions.NONE
|
|
213
|
-
);
|
|
214
|
-
} else {
|
|
215
|
-
const lastSlash = this.#path.lastIndexOf("/");
|
|
216
|
-
const dir = lastSlash >= 0 ? this.#path.substring(0, lastSlash) : ".";
|
|
217
|
-
const name = lastSlash >= 0 ? this.#path.substring(lastSlash + 1) : this.#path;
|
|
218
|
-
const cncString = `DB_DIR=${dir};DB_NAME=${name}`;
|
|
219
|
-
const connOpts = this.#options.readOnly ? Gda.ConnectionOptions.READ_ONLY : Gda.ConnectionOptions.NONE;
|
|
220
|
-
this.#connection = Gda.Connection.new_from_string(
|
|
221
|
-
"SQLite",
|
|
222
|
-
cncString,
|
|
223
|
-
null,
|
|
224
|
-
connOpts
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
this.#connection.open();
|
|
228
|
-
} catch (e) {
|
|
229
|
-
this.#connection = null;
|
|
230
|
-
throw new SqliteError(
|
|
231
|
-
e instanceof Error ? e.message : String(e)
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
this.#parser = this.#connection.create_parser() ?? new Gda.SqlParser();
|
|
235
|
-
this.#applyPragmas();
|
|
236
|
-
return void 0;
|
|
237
|
-
}
|
|
238
|
-
close() {
|
|
239
|
-
if (!this.isOpen) {
|
|
240
|
-
throw new InvalidStateError("database is not open");
|
|
241
|
-
}
|
|
242
|
-
this.#connection.close();
|
|
243
|
-
this.#connection = null;
|
|
244
|
-
this.#parser = null;
|
|
245
|
-
this.#inTransaction = false;
|
|
246
|
-
return void 0;
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Execute one or more SQL statements. Returns undefined.
|
|
250
|
-
* Named sqlExec to avoid security hook false positive on "exec" name.
|
|
251
|
-
*/
|
|
252
|
-
sqlExec(sql) {
|
|
253
|
-
this.#ensureOpen();
|
|
254
|
-
if (typeof sql !== "string") {
|
|
255
|
-
throw new InvalidArgTypeError('The "sql" argument must be a string.');
|
|
256
|
-
}
|
|
257
|
-
try {
|
|
258
|
-
const statements = this.#splitStatements(sql);
|
|
259
|
-
for (const stmtSql of statements) {
|
|
260
|
-
const [stmt] = this.#parser.parse_string(stmtSql);
|
|
261
|
-
if (stmt) {
|
|
262
|
-
this.#executeStatement(stmt);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
} catch (e) {
|
|
266
|
-
if (e instanceof SqliteError || e instanceof InvalidStateError || e instanceof InvalidArgTypeError) {
|
|
267
|
-
throw e;
|
|
268
|
-
}
|
|
269
|
-
throw new SqliteError(
|
|
270
|
-
e instanceof Error ? e.message : String(e)
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
this.#updateTransactionState(sql);
|
|
274
|
-
return void 0;
|
|
275
|
-
}
|
|
276
|
-
prepare(sql, options) {
|
|
277
|
-
this.#ensureOpen();
|
|
278
|
-
if (typeof sql !== "string") {
|
|
279
|
-
throw new InvalidArgTypeError('The "sql" argument must be a string.');
|
|
280
|
-
}
|
|
281
|
-
const [, paramMap] = convertParameterSyntax(sql);
|
|
282
|
-
try {
|
|
283
|
-
const testSql = paramMap.length > 0 ? sql.replace(/\?(\d+)?/g, "NULL").replace(/[\$:@][a-zA-Z_][a-zA-Z0-9_]*/g, "NULL") : sql;
|
|
284
|
-
const [stmt] = this.#parser.parse_string(testSql);
|
|
285
|
-
if (!stmt) {
|
|
286
|
-
throw new SqliteError("Failed to parse SQL statement");
|
|
287
|
-
}
|
|
288
|
-
} catch (e) {
|
|
289
|
-
if (e instanceof SqliteError || e instanceof InvalidArgTypeError) {
|
|
290
|
-
throw e;
|
|
291
|
-
}
|
|
292
|
-
throw new SqliteError(
|
|
293
|
-
e instanceof Error ? e.message : String(e)
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
const stmtOptions = {
|
|
297
|
-
readBigInts: this.#options.readBigInts ?? false,
|
|
298
|
-
returnArrays: this.#options.returnArrays ?? false,
|
|
299
|
-
allowBareNamedParameters: this.#options.allowBareNamedParameters ?? true,
|
|
300
|
-
allowUnknownNamedParameters: this.#options.allowUnknownNamedParameters ?? false
|
|
301
|
-
};
|
|
302
|
-
return StatementSync._create(this.#connection, sql, stmtOptions, paramMap, this.#parser);
|
|
303
|
-
}
|
|
304
|
-
location(dbName) {
|
|
305
|
-
this.#ensureOpen();
|
|
306
|
-
if (dbName !== void 0 && typeof dbName !== "string") {
|
|
307
|
-
throw new InvalidArgTypeError('The "dbName" argument must be a string.');
|
|
308
|
-
}
|
|
309
|
-
if (this.#isMemory) {
|
|
310
|
-
return null;
|
|
311
|
-
}
|
|
312
|
-
return this.#path;
|
|
313
|
-
}
|
|
314
|
-
[Symbol.dispose]() {
|
|
315
|
-
if (this.isOpen) {
|
|
316
|
-
try {
|
|
317
|
-
this.close();
|
|
318
|
-
} catch {
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
#ensureOpen() {
|
|
323
|
-
if (!this.isOpen) {
|
|
324
|
-
throw new InvalidStateError("database is not open");
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
#applyPragmas() {
|
|
328
|
-
const conn = this.#connection;
|
|
329
|
-
const runPragma = (pragma) => {
|
|
330
|
-
const parser = this.#parser;
|
|
331
|
-
const [stmt] = parser.parse_string(pragma);
|
|
332
|
-
if (stmt) {
|
|
333
|
-
conn.statement_execute_select(stmt, null);
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
if (this.#options.enableForeignKeyConstraints !== false) {
|
|
337
|
-
runPragma("PRAGMA foreign_keys = ON");
|
|
338
|
-
} else {
|
|
339
|
-
runPragma("PRAGMA foreign_keys = OFF");
|
|
340
|
-
}
|
|
341
|
-
if (this.#options.timeout !== void 0 && this.#options.timeout > 0) {
|
|
342
|
-
runPragma(`PRAGMA busy_timeout = ${this.#options.timeout}`);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
#executeStatement(stmt) {
|
|
346
|
-
const stmtType = stmt.get_statement_type();
|
|
347
|
-
if (stmtType === Gda.SqlStatementType.SELECT) {
|
|
348
|
-
this.#connection.statement_execute_select(stmt, null);
|
|
349
|
-
} else {
|
|
350
|
-
try {
|
|
351
|
-
this.#connection.statement_execute_non_select(stmt, null);
|
|
352
|
-
} catch {
|
|
353
|
-
this.#connection.statement_execute_select(stmt, null);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
#splitStatements(sql) {
|
|
358
|
-
const stmts = [];
|
|
359
|
-
let current = "";
|
|
360
|
-
let inString = false;
|
|
361
|
-
for (let i = 0; i < sql.length; i++) {
|
|
362
|
-
const ch = sql[i];
|
|
363
|
-
if (ch === "'" && !inString) {
|
|
364
|
-
inString = true;
|
|
365
|
-
current += ch;
|
|
366
|
-
} else if (ch === "'" && inString) {
|
|
367
|
-
if (sql[i + 1] === "'") {
|
|
368
|
-
current += "''";
|
|
369
|
-
i++;
|
|
370
|
-
} else {
|
|
371
|
-
inString = false;
|
|
372
|
-
current += ch;
|
|
373
|
-
}
|
|
374
|
-
} else if (ch === ";" && !inString) {
|
|
375
|
-
const trimmed2 = current.trim();
|
|
376
|
-
if (trimmed2.length > 0) {
|
|
377
|
-
stmts.push(trimmed2);
|
|
378
|
-
}
|
|
379
|
-
current = "";
|
|
380
|
-
} else {
|
|
381
|
-
current += ch;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
const trimmed = current.trim();
|
|
385
|
-
if (trimmed.length > 0) {
|
|
386
|
-
stmts.push(trimmed);
|
|
387
|
-
}
|
|
388
|
-
return stmts;
|
|
389
|
-
}
|
|
390
|
-
#updateTransactionState(sql) {
|
|
391
|
-
const trimmed = sql.trim().toUpperCase();
|
|
392
|
-
if (/\bBEGIN\b/.test(trimmed)) {
|
|
393
|
-
this.#inTransaction = true;
|
|
394
|
-
}
|
|
395
|
-
if (/\bCOMMIT\b/.test(trimmed) || /\bROLLBACK\b/.test(trimmed)) {
|
|
396
|
-
this.#inTransaction = false;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
110
|
+
const params = [];
|
|
111
|
+
let positionalIndex = 0;
|
|
112
|
+
let result = "";
|
|
113
|
+
let i = 0;
|
|
114
|
+
while (i < sql.length) {
|
|
115
|
+
if (sql[i] === "'") {
|
|
116
|
+
const start = i;
|
|
117
|
+
i++;
|
|
118
|
+
while (i < sql.length && sql[i] !== "'") {
|
|
119
|
+
if (sql[i] === "'" && sql[i + 1] === "'") {
|
|
120
|
+
i += 2;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
i++;
|
|
124
|
+
}
|
|
125
|
+
if (i < sql.length) i++;
|
|
126
|
+
result += sql.substring(start, i);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (sql[i] === "?") {
|
|
130
|
+
i++;
|
|
131
|
+
let numStr = "";
|
|
132
|
+
while (i < sql.length && sql[i] >= "0" && sql[i] <= "9") {
|
|
133
|
+
numStr += sql[i];
|
|
134
|
+
i++;
|
|
135
|
+
}
|
|
136
|
+
const pos = numStr ? parseInt(numStr, 10) - 1 : positionalIndex;
|
|
137
|
+
positionalIndex = numStr ? positionalIndex : positionalIndex + 1;
|
|
138
|
+
const gdaId = `p${pos}`;
|
|
139
|
+
params.push({
|
|
140
|
+
gdaId,
|
|
141
|
+
originalName: numStr ? `?${numStr}` : "?",
|
|
142
|
+
position: pos
|
|
143
|
+
});
|
|
144
|
+
result += `##${gdaId}::string`;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if ((sql[i] === "$" || sql[i] === ":" || sql[i] === "@") && i + 1 < sql.length && /[a-zA-Z_]/.test(sql[i + 1])) {
|
|
148
|
+
const prefix = sql[i];
|
|
149
|
+
i++;
|
|
150
|
+
let name = "";
|
|
151
|
+
while (i < sql.length && /[a-zA-Z0-9_]/.test(sql[i])) {
|
|
152
|
+
name += sql[i];
|
|
153
|
+
i++;
|
|
154
|
+
}
|
|
155
|
+
const gdaId = name;
|
|
156
|
+
params.push({
|
|
157
|
+
gdaId,
|
|
158
|
+
originalName: `${prefix}${name}`,
|
|
159
|
+
position: -1
|
|
160
|
+
});
|
|
161
|
+
result += `##${gdaId}::string`;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
result += sql[i];
|
|
165
|
+
i++;
|
|
166
|
+
}
|
|
167
|
+
return [result, params];
|
|
399
168
|
}
|
|
400
|
-
DatabaseSync
|
|
401
|
-
|
|
402
|
-
|
|
169
|
+
var DatabaseSync = class {
|
|
170
|
+
#connection = null;
|
|
171
|
+
#parser = null;
|
|
172
|
+
#path;
|
|
173
|
+
#options;
|
|
174
|
+
#isMemory;
|
|
175
|
+
#inTransaction = false;
|
|
176
|
+
constructor(path, options) {
|
|
177
|
+
if (!new.target) {
|
|
178
|
+
throw new ConstructCallRequiredError("DatabaseSync");
|
|
179
|
+
}
|
|
180
|
+
this.#path = parsePath(path);
|
|
181
|
+
this.#options = validateOptions(options);
|
|
182
|
+
this.#isMemory = this.#path === ":memory:";
|
|
183
|
+
const shouldOpen = this.#options.open !== false;
|
|
184
|
+
if (shouldOpen) {
|
|
185
|
+
this.open();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
get [sqliteTypeSymbol]() {
|
|
189
|
+
return "node:sqlite";
|
|
190
|
+
}
|
|
191
|
+
get isOpen() {
|
|
192
|
+
return this.#connection !== null && this.#connection.is_opened();
|
|
193
|
+
}
|
|
194
|
+
get isTransaction() {
|
|
195
|
+
this.#ensureOpen();
|
|
196
|
+
return this.#inTransaction;
|
|
197
|
+
}
|
|
198
|
+
open() {
|
|
199
|
+
if (this.isOpen) {
|
|
200
|
+
throw new InvalidStateError("database is already open");
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
if (this.#isMemory) {
|
|
204
|
+
this.#connection = Gda.Connection.new_from_string("SQLite", "DB_DIR=;DB_NAME=:memory:", null, Gda.ConnectionOptions.NONE);
|
|
205
|
+
} else {
|
|
206
|
+
const lastSlash = this.#path.lastIndexOf("/");
|
|
207
|
+
const dir = lastSlash >= 0 ? this.#path.substring(0, lastSlash) : ".";
|
|
208
|
+
const name = lastSlash >= 0 ? this.#path.substring(lastSlash + 1) : this.#path;
|
|
209
|
+
const cncString = `DB_DIR=${dir};DB_NAME=${name}`;
|
|
210
|
+
const connOpts = this.#options.readOnly ? Gda.ConnectionOptions.READ_ONLY : Gda.ConnectionOptions.NONE;
|
|
211
|
+
this.#connection = Gda.Connection.new_from_string("SQLite", cncString, null, connOpts);
|
|
212
|
+
}
|
|
213
|
+
this.#connection.open();
|
|
214
|
+
} catch (e) {
|
|
215
|
+
this.#connection = null;
|
|
216
|
+
throw new SqliteError(e instanceof Error ? e.message : String(e));
|
|
217
|
+
}
|
|
218
|
+
this.#parser = this.#connection.create_parser() ?? new Gda.SqlParser();
|
|
219
|
+
this.#applyPragmas();
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
close() {
|
|
223
|
+
if (!this.isOpen) {
|
|
224
|
+
throw new InvalidStateError("database is not open");
|
|
225
|
+
}
|
|
226
|
+
this.#connection.close();
|
|
227
|
+
this.#connection = null;
|
|
228
|
+
this.#parser = null;
|
|
229
|
+
this.#inTransaction = false;
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Execute one or more SQL statements. Returns undefined.
|
|
234
|
+
* Named sqlExec to avoid security hook false positive on "exec" name.
|
|
235
|
+
*/
|
|
236
|
+
sqlExec(sql) {
|
|
237
|
+
this.#ensureOpen();
|
|
238
|
+
if (typeof sql !== "string") {
|
|
239
|
+
throw new InvalidArgTypeError("The \"sql\" argument must be a string.");
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const statements = this.#splitStatements(sql);
|
|
243
|
+
for (const stmtSql of statements) {
|
|
244
|
+
const [stmt] = this.#parser.parse_string(stmtSql);
|
|
245
|
+
if (stmt) {
|
|
246
|
+
this.#executeStatement(stmt);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
} catch (e) {
|
|
250
|
+
if (e instanceof SqliteError || e instanceof InvalidStateError || e instanceof InvalidArgTypeError) {
|
|
251
|
+
throw e;
|
|
252
|
+
}
|
|
253
|
+
throw new SqliteError(e instanceof Error ? e.message : String(e));
|
|
254
|
+
}
|
|
255
|
+
this.#updateTransactionState(sql);
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
prepare(sql, options) {
|
|
259
|
+
this.#ensureOpen();
|
|
260
|
+
if (typeof sql !== "string") {
|
|
261
|
+
throw new InvalidArgTypeError("The \"sql\" argument must be a string.");
|
|
262
|
+
}
|
|
263
|
+
const [, paramMap] = convertParameterSyntax(sql);
|
|
264
|
+
try {
|
|
265
|
+
const testSql = paramMap.length > 0 ? sql.replace(/\?(\d+)?/g, "NULL").replace(/[\$:@][a-zA-Z_][a-zA-Z0-9_]*/g, "NULL") : sql;
|
|
266
|
+
const [stmt] = this.#parser.parse_string(testSql);
|
|
267
|
+
if (!stmt) {
|
|
268
|
+
throw new SqliteError("Failed to parse SQL statement");
|
|
269
|
+
}
|
|
270
|
+
} catch (e) {
|
|
271
|
+
if (e instanceof SqliteError || e instanceof InvalidArgTypeError) {
|
|
272
|
+
throw e;
|
|
273
|
+
}
|
|
274
|
+
throw new SqliteError(e instanceof Error ? e.message : String(e));
|
|
275
|
+
}
|
|
276
|
+
const stmtOptions = {
|
|
277
|
+
readBigInts: this.#options.readBigInts ?? false,
|
|
278
|
+
returnArrays: this.#options.returnArrays ?? false,
|
|
279
|
+
allowBareNamedParameters: this.#options.allowBareNamedParameters ?? true,
|
|
280
|
+
allowUnknownNamedParameters: this.#options.allowUnknownNamedParameters ?? false
|
|
281
|
+
};
|
|
282
|
+
return StatementSync._create(this.#connection, sql, stmtOptions, paramMap, this.#parser);
|
|
283
|
+
}
|
|
284
|
+
location(dbName) {
|
|
285
|
+
this.#ensureOpen();
|
|
286
|
+
if (dbName !== undefined && typeof dbName !== "string") {
|
|
287
|
+
throw new InvalidArgTypeError("The \"dbName\" argument must be a string.");
|
|
288
|
+
}
|
|
289
|
+
if (this.#isMemory) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
return this.#path;
|
|
293
|
+
}
|
|
294
|
+
[Symbol.dispose]() {
|
|
295
|
+
if (this.isOpen) {
|
|
296
|
+
try {
|
|
297
|
+
this.close();
|
|
298
|
+
} catch {}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
#ensureOpen() {
|
|
302
|
+
if (!this.isOpen) {
|
|
303
|
+
throw new InvalidStateError("database is not open");
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
#applyPragmas() {
|
|
307
|
+
const conn = this.#connection;
|
|
308
|
+
const runPragma = (pragma) => {
|
|
309
|
+
const parser = this.#parser;
|
|
310
|
+
const [stmt] = parser.parse_string(pragma);
|
|
311
|
+
if (stmt) {
|
|
312
|
+
conn.statement_execute_select(stmt, null);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
if (this.#options.enableForeignKeyConstraints !== false) {
|
|
316
|
+
runPragma("PRAGMA foreign_keys = ON");
|
|
317
|
+
} else {
|
|
318
|
+
runPragma("PRAGMA foreign_keys = OFF");
|
|
319
|
+
}
|
|
320
|
+
if (this.#options.timeout !== undefined && this.#options.timeout > 0) {
|
|
321
|
+
runPragma(`PRAGMA busy_timeout = ${this.#options.timeout}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
#executeStatement(stmt) {
|
|
325
|
+
const stmtType = stmt.get_statement_type();
|
|
326
|
+
if (stmtType === Gda.SqlStatementType.SELECT) {
|
|
327
|
+
this.#connection.statement_execute_select(stmt, null);
|
|
328
|
+
} else {
|
|
329
|
+
try {
|
|
330
|
+
this.#connection.statement_execute_non_select(stmt, null);
|
|
331
|
+
} catch {
|
|
332
|
+
this.#connection.statement_execute_select(stmt, null);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
#splitStatements(sql) {
|
|
337
|
+
const stmts = [];
|
|
338
|
+
let current = "";
|
|
339
|
+
let inString = false;
|
|
340
|
+
for (let i = 0; i < sql.length; i++) {
|
|
341
|
+
const ch = sql[i];
|
|
342
|
+
if (ch === "'" && !inString) {
|
|
343
|
+
inString = true;
|
|
344
|
+
current += ch;
|
|
345
|
+
} else if (ch === "'" && inString) {
|
|
346
|
+
if (sql[i + 1] === "'") {
|
|
347
|
+
current += "''";
|
|
348
|
+
i++;
|
|
349
|
+
} else {
|
|
350
|
+
inString = false;
|
|
351
|
+
current += ch;
|
|
352
|
+
}
|
|
353
|
+
} else if (ch === ";" && !inString) {
|
|
354
|
+
const trimmed = current.trim();
|
|
355
|
+
if (trimmed.length > 0) {
|
|
356
|
+
stmts.push(trimmed);
|
|
357
|
+
}
|
|
358
|
+
current = "";
|
|
359
|
+
} else {
|
|
360
|
+
current += ch;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const trimmed = current.trim();
|
|
364
|
+
if (trimmed.length > 0) {
|
|
365
|
+
stmts.push(trimmed);
|
|
366
|
+
}
|
|
367
|
+
return stmts;
|
|
368
|
+
}
|
|
369
|
+
#updateTransactionState(sql) {
|
|
370
|
+
const trimmed = sql.trim().toUpperCase();
|
|
371
|
+
if (/\bBEGIN\b/.test(trimmed)) {
|
|
372
|
+
this.#inTransaction = true;
|
|
373
|
+
}
|
|
374
|
+
if (/\bCOMMIT\b/.test(trimmed) || /\bROLLBACK\b/.test(trimmed)) {
|
|
375
|
+
this.#inTransaction = false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
403
378
|
};
|
|
379
|
+
DatabaseSync.prototype.exec = DatabaseSync.prototype.sqlExec;
|
|
380
|
+
|
|
381
|
+
//#endregion
|
|
382
|
+
export { DatabaseSync };
|