sqlmath 2022.3.5 → 2022.6.30

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/sqlmath.mjs CHANGED
@@ -1,17 +1,112 @@
1
+ // MIT License
2
+ //
3
+ // Copyright (c) 2021 Kai Zhu
4
+ //
5
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ // of this software and associated documentation files (the "Software"), to deal
7
+ // in the Software without restriction, including without limitation the rights
8
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ // copies of the Software, and to permit persons to whom the Software is
10
+ // furnished to do so, subject to the following conditions:
11
+ //
12
+ // The above copyright notice and this permission notice shall be included in
13
+ // all copies or substantial portions of the Software.
14
+ //
15
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ // SOFTWARE.
22
+
23
+
1
24
  /*jslint beta, bitwise, name, node*/
25
+ /*global FinalizationRegistry*/
2
26
  "use strict";
3
- import {createRequire} from "module";
4
- import jslint from "./jslint.mjs";
5
27
 
6
- let {
7
- assertErrorThrownAsync,
8
- assertJsonEqual,
9
- assertOrThrow,
10
- debugInline,
11
- noop,
12
- objectDeepCopyWithKeysSorted
13
- } = jslint;
14
- let local = Object.assign({}, jslint);
28
+ let FILENAME_DBTMP = "/tmp/__dbtmp1";
29
+ let IS_BROWSER;
30
+ let JSBATON_ARGC = 16;
31
+ let SQLITE_DATATYPE_BLOB = 0x04;
32
+ // let SQLITE_DATATYPE_BLOB_0 = 0x14;
33
+ let SQLITE_DATATYPE_EXTERNALBUFFER = -0x01;
34
+ let SQLITE_DATATYPE_FLOAT = 0x02;
35
+ // let SQLITE_DATATYPE_FLOAT_0 = 0x12;
36
+ let SQLITE_DATATYPE_INTEGER = 0x01;
37
+ let SQLITE_DATATYPE_INTEGER_0 = 0x11;
38
+ let SQLITE_DATATYPE_INTEGER_1 = 0x21;
39
+ let SQLITE_DATATYPE_NULL = 0x05;
40
+ let SQLITE_DATATYPE_OFFSET = 768;
41
+ let SQLITE_DATATYPE_TEXT = 0x03;
42
+ let SQLITE_DATATYPE_TEXT_0 = 0x13;
43
+ let SQLITE_MAX_LENGTH2 = 1_000_000_000;
44
+ let SQLITE_OPEN_AUTOPROXY = 0x00000020; /* VFS only */
45
+ let SQLITE_OPEN_CREATE = 0x00000004; /* Ok for sqlite3_open_v2() */
46
+ let SQLITE_OPEN_DELETEONCLOSE = 0x00000008; /* VFS only */
47
+ let SQLITE_OPEN_EXCLUSIVE = 0x00000010; /* VFS only */
48
+ let SQLITE_OPEN_FULLMUTEX = 0x00010000; /* Ok for sqlite3_open_v2() */
49
+ let SQLITE_OPEN_MAIN_DB = 0x00000100; /* VFS only */
50
+ let SQLITE_OPEN_MAIN_JOURNAL = 0x00000800; /* VFS only */
51
+ let SQLITE_OPEN_MEMORY = 0x00000080; /* Ok for sqlite3_open_v2() */
52
+ let SQLITE_OPEN_NOFOLLOW = 0x01000000; /* Ok for sqlite3_open_v2() */
53
+ let SQLITE_OPEN_NOMUTEX = 0x00008000; /* Ok for sqlite3_open_v2() */
54
+ let SQLITE_OPEN_PRIVATECACHE = 0x00040000; /* Ok for sqlite3_open_v2() */
55
+ let SQLITE_OPEN_READONLY = 0x00000001; /* Ok for sqlite3_open_v2() */
56
+ let SQLITE_OPEN_READWRITE = 0x00000002; /* Ok for sqlite3_open_v2() */
57
+ let SQLITE_OPEN_SHAREDCACHE = 0x00020000; /* Ok for sqlite3_open_v2() */
58
+ let SQLITE_OPEN_SUBJOURNAL = 0x00002000; /* VFS only */
59
+ let SQLITE_OPEN_SUPER_JOURNAL = 0x00004000; /* VFS only */
60
+ let SQLITE_OPEN_TEMP_DB = 0x00000200; /* VFS only */
61
+ let SQLITE_OPEN_TEMP_JOURNAL = 0x00001000; /* VFS only */
62
+ let SQLITE_OPEN_TRANSIENT_DB = 0x00000400; /* VFS only */
63
+ let SQLITE_OPEN_URI = 0x00000040; /* Ok for sqlite3_open_v2() */
64
+ let SQLITE_OPEN_WAL = 0x00080000; /* VFS only */
65
+ let cModule;
66
+ let consoleError = console.error;
67
+ let dbDict = new WeakMap(); // private-dict of sqlite-database-connections
68
+ let dbFinalizationRegistry;
69
+ // init debugInline
70
+ let debugInline = (function () {
71
+ let __consoleError = function () {
72
+ return;
73
+ };
74
+ function debug(...argv) {
75
+
76
+ // This function will print <argv> to stderr and then return <argv>[0].
77
+
78
+ __consoleError("\n\ndebugInline");
79
+ __consoleError(...argv);
80
+ __consoleError("\n");
81
+ return argv[0];
82
+ }
83
+ debug(); // Coverage-hack.
84
+ __consoleError = console.error;
85
+ return debug;
86
+ }());
87
+ let sqlMessageDict = {}; // dict of web-worker-callbacks
88
+ let sqlMessageId = 0;
89
+ let sqlWorker;
90
+
91
+ function assertJsonEqual(aa, bb, message) {
92
+
93
+ // This function will assert JSON.stringify(<aa>) === JSON.stringify(<bb>).
94
+
95
+ aa = JSON.stringify(objectDeepCopyWithKeysSorted(aa), undefined, 1);
96
+ bb = JSON.stringify(objectDeepCopyWithKeysSorted(bb), undefined, 1);
97
+ if (aa !== bb) {
98
+ throw new Error(
99
+ "\n" + aa + "\n!==\n" + bb
100
+ + (
101
+ typeof message === "string"
102
+ ? " - " + message
103
+ : message
104
+ ? " - " + JSON.stringify(message)
105
+ : ""
106
+ )
107
+ );
108
+ }
109
+ }
15
110
 
16
111
  function assertNumericalEqual(aa, bb, message) {
17
112
 
@@ -29,293 +124,324 @@ function assertNumericalEqual(aa, bb, message) {
29
124
  }
30
125
  }
31
126
 
127
+ function assertOrThrow(condition, message) {
32
128
 
33
- /*
34
- file sqlmath.js
35
- */
36
- (function () {
37
- let JSBATON_ARGC = 16;
38
- // let SIZEOF_MESSAGE_DEFAULT = 768;
39
- let SQLITE_MAX_LENGTH2 = 1000000000;
40
- let SQLITE_OPEN_AUTOPROXY = 0x00000020; /* VFS only */
41
- let SQLITE_OPEN_CREATE = 0x00000004; /* Ok for sqlite3_open_v2() */
42
- let SQLITE_OPEN_DELETEONCLOSE = 0x00000008; /* VFS only */
43
- let SQLITE_OPEN_EXCLUSIVE = 0x00000010; /* VFS only */
44
- let SQLITE_OPEN_FULLMUTEX = 0x00010000; /* Ok for sqlite3_open_v2() */
45
- let SQLITE_OPEN_MAIN_DB = 0x00000100; /* VFS only */
46
- let SQLITE_OPEN_MAIN_JOURNAL = 0x00000800; /* VFS only */
47
- let SQLITE_OPEN_MEMORY = 0x00000080; /* Ok for sqlite3_open_v2() */
48
- let SQLITE_OPEN_NOFOLLOW = 0x01000000; /* Ok for sqlite3_open_v2() */
49
- let SQLITE_OPEN_NOMUTEX = 0x00008000; /* Ok for sqlite3_open_v2() */
50
- let SQLITE_OPEN_PRIVATECACHE = 0x00040000; /* Ok for sqlite3_open_v2() */
51
- let SQLITE_OPEN_READONLY = 0x00000001; /* Ok for sqlite3_open_v2() */
52
- let SQLITE_OPEN_READWRITE = 0x00000002; /* Ok for sqlite3_open_v2() */
53
- let SQLITE_OPEN_SHAREDCACHE = 0x00020000; /* Ok for sqlite3_open_v2() */
54
- let SQLITE_OPEN_SUBJOURNAL = 0x00002000; /* VFS only */
55
- let SQLITE_OPEN_SUPER_JOURNAL = 0x00004000; /* VFS only */
56
- let SQLITE_OPEN_TEMP_DB = 0x00000200; /* VFS only */
57
- let SQLITE_OPEN_TEMP_JOURNAL = 0x00001000; /* VFS only */
58
- let SQLITE_OPEN_TRANSIENT_DB = 0x00000400; /* VFS only */
59
- let SQLITE_OPEN_URI = 0x00000040; /* Ok for sqlite3_open_v2() */
60
- let SQLITE_OPEN_WAL = 0x00080000; /* VFS only */
61
- let addon;
62
- let consoleError = console.error;
63
- let dbDict = new WeakMap(); // private map of sqlite-database-connections
64
- let requireCjs = createRequire(import.meta.url);
65
- let testList;
129
+ // This function will throw <message> if <condition> is falsy.
130
+
131
+ if (!condition) {
132
+ throw (
133
+ (!message || typeof message === "string")
134
+ ? new Error(String(message).slice(0, 2048))
135
+ : message
136
+ );
137
+ }
138
+ }
66
139
 
67
- function cCall(func, argList) {
140
+ async function cCallAsync(baton, cFuncName, ...argList) {
68
141
  // this function will serialize <argList> to a c <baton>,
69
142
  // suitable for passing into napi
70
- let baton = new BigInt64Array(2048);
71
- let errStack;
72
- let result;
73
- // serialize js-args to c-args
74
- argList = argList.map(function (arg, ii) {
75
- switch (typeof arg) {
76
- case "bigint":
77
- case "boolean":
78
- case "number":
79
- try {
80
- baton[ii] = BigInt(arg);
81
- } catch (ignore) {
82
- return;
83
- }
84
- break;
85
- // case "object":
86
- // break;
87
- case "string":
88
- // append null-terminator to string
89
- arg = new TextEncoder().encode(arg + "\u0000");
90
- break;
91
- }
92
- if (ArrayBuffer.isView(arg)) {
93
- baton[ii] = BigInt(arg.byteLength);
94
- return new DataView(
95
- arg.buffer,
96
- arg.byteOffset,
97
- arg.byteLength
98
- );
99
- }
100
- return arg;
101
- });
102
- // pad argList to length = JSBATON_ARGC
103
- argList = argList.concat(
104
- Array.from(new Array(JSBATON_ARGC))
105
- ).slice(0, JSBATON_ARGC);
106
- // prepend baton to argList
107
- argList.unshift(baton);
108
- // call napi with func and argList
109
- result = addon[func](argList);
110
- if (typeof result?.catch === "function") {
111
- errStack = new Error().stack.replace((
112
- /.*$/m
113
- ), "");
114
- return result.catch(function (err) {
115
- err.stack += errStack;
116
- throw err;
143
+ let argi = 0;
144
+ let errStack;
145
+ assertOrThrow(
146
+ argList.length < 16,
147
+ "cCallAsync - argList.length must be less than than 16"
148
+ );
149
+ baton = baton || jsbatonCreate();
150
+ // pad argList to length JSBATON_ARGC
151
+ while (argList.length < 2 * JSBATON_ARGC) {
152
+ argList.push(0n);
153
+ }
154
+ // serialize js-value to c-value
155
+ argList = argList.map(function (value, ii) {
156
+ argi = ii;
157
+ switch (typeof value) {
158
+ case "bigint":
159
+ case "boolean":
160
+ baton.setBigInt64(8 + argi * 8, BigInt(value), true);
161
+ return value;
162
+ case "number":
163
+ // check for min/max safe-integer
164
+ assertOrThrow(
165
+ (
166
+ -9_007_199_254_740_991 <= value
167
+ && value <= 9_007_199_254_740_991
168
+ ),
169
+ (
170
+ "non-bigint integer must be within inclusive-range"
171
+ + " -9,007,199,254,740,991 to 9,007,199,254,740,991"
172
+ )
173
+ );
174
+ baton.setBigInt64(8 + argi * 8, BigInt(value), true);
175
+ return value;
176
+ // case "object":
177
+ // break;
178
+ case "string":
179
+ baton = jsbatonValuePush({
180
+ argi,
181
+ baton,
182
+ value: (
183
+ value.endsWith("\u0000")
184
+ ? value
185
+ // append null-terminator to string
186
+ : value + "\u0000"
187
+ )
117
188
  });
189
+ return;
118
190
  }
119
- return result;
191
+ if (ArrayBuffer.isView(value)) {
192
+ return new DataView(
193
+ value.buffer,
194
+ value.byteOffset,
195
+ value.byteLength
196
+ );
197
+ }
198
+ if (isExternalBuffer(value)) {
199
+ return value;
200
+ }
201
+ });
202
+ // encode cFuncName into baton
203
+ argi += 1;
204
+ baton = jsbatonValuePush({
205
+ argi,
206
+ baton,
207
+ value: cFuncName + "\u0000"
208
+ });
209
+ // prepend baton, cFuncName to argList
210
+ argList = [
211
+ baton, cFuncName, ...argList
212
+ ];
213
+ // preserve stack-trace
214
+ errStack = new Error().stack.replace((
215
+ /.*$/m
216
+ ), "");
217
+ try {
218
+ return (
219
+ IS_BROWSER
220
+ ? await sqlMessagePost(...argList)
221
+ : await cModule[cFuncName](argList)
222
+ );
223
+ } catch (err) {
224
+ err.stack += errStack;
225
+ assertOrThrow(undefined, err);
120
226
  }
227
+ }
121
228
 
122
- function dbCallAsync(func, db, argList) {
123
- // this function will call <func> using <db>
124
- db = dbDeref(db);
125
- // increment db.busy
126
- db.busy += 1;
127
- return cCall(func, [
128
- db.ptr
129
- ].concat(argList)).finally(function () {
130
- // decrement db.busy
131
- db.busy -= 1;
132
- assertOrThrow(db.busy >= 0, "invalid db.busy " + db.busy);
133
- });
134
- }
229
+ function dbCallAsync(baton, cFuncName, db, ...argList) {
230
+ // this function will call <cFuncName> using db <argList>[0]
231
+ let __db = dbDeref(db);
232
+ // increment __db.busy
233
+ __db.busy += 1;
234
+ return cCallAsync(
235
+ baton,
236
+ cFuncName,
237
+ __db.ptr,
238
+ ...argList
239
+ ).finally(function () {
240
+ // decrement __db.busy
241
+ __db.busy -= 1;
242
+ assertOrThrow(__db.busy >= 0, `invalid __db.busy ${__db.busy}`);
243
+ });
244
+ }
135
245
 
136
- async function dbCloseAsync({
137
- db
138
- }) {
246
+ async function dbCloseAsync({
247
+ db
248
+ }) {
139
249
  // this function will close sqlite-database-connection <db>
140
- let __db = dbDeref(db);
141
- // prevent segfault - do not close db if actions are pending
142
- assertOrThrow(
143
- __db.busy === 0,
144
- "db cannot close with " + __db.busy + " actions pending"
250
+ let __db = dbDeref(db);
251
+ // prevent segfault - do not close db if actions are pending
252
+ assertOrThrow(
253
+ __db.busy === 0,
254
+ "db cannot close with " + __db.busy + " actions pending"
255
+ );
256
+ // cleanup connPool
257
+ await Promise.all(__db.connPool.map(async function (ptr) {
258
+ let val = ptr[0];
259
+ ptr[0] = 0n;
260
+ await cCallAsync(
261
+ undefined,
262
+ "_dbClose",
263
+ val,
264
+ __db.filename
145
265
  );
146
- // cleanup connPool
147
- await Promise.all(__db.connPool.map(async function (ptr) {
148
- let val = ptr[0];
149
- ptr[0] = 0n;
150
- await cCall("__dbCloseAsync", [
151
- val
152
- ]);
153
- }));
154
- dbDict.delete(db);
155
- }
266
+ }));
267
+ dbDict.delete(db);
268
+ }
156
269
 
157
- function dbDeref(db) {
270
+ function dbDeref(db) {
158
271
  // this function will get private-object mapped to <db>
159
- let __db = dbDict.get(db);
160
- assertOrThrow(__db?.connPool[0] > 0, "invalid or closed db");
161
- assertOrThrow(__db.busy >= 0, "invalid db.busy " + __db.busy);
162
- __db.ii = (__db.ii + 1) % __db.connPool.length;
163
- __db.ptr = __db.connPool[__db.ii][0];
164
- assertOrThrow(__db.ptr > 0n, "invalid or closed db");
165
- return __db;
166
- }
272
+ let __db = dbDict.get(db);
273
+ assertOrThrow(__db?.connPool[0] > 0, "invalid or closed db");
274
+ assertOrThrow(__db.busy >= 0, "invalid db.busy " + __db.busy);
275
+ __db.ii = (__db.ii + 1) % __db.connPool.length;
276
+ __db.ptr = __db.connPool[__db.ii][0];
277
+ assertOrThrow(__db.ptr > 0n, "invalid or closed db");
278
+ return __db;
279
+ }
167
280
 
168
- async function dbExecAsync({
169
- bindList = [],
281
+ function dbExecAndReturnLastBlobAsync({
282
+ bindList = [],
283
+ db,
284
+ sql
285
+ }) {
286
+ // this function will exec <sql> in <db> and return last value retrieved
287
+ // from execution as raw blob/buffer
288
+ return dbExecAsync({
289
+ bindList,
170
290
  db,
171
- responseType,
172
- sql,
173
- tmpColList,
174
- tmpColListPriority,
175
- tmpCsv,
176
- tmpRowList,
177
- tmpTableName
178
- }) {
291
+ responseType: "lastBlob",
292
+ sql
293
+ });
294
+ }
295
+
296
+ async function dbExecAsync({
297
+ bindList = [],
298
+ db,
299
+ modeRetry,
300
+ responseType,
301
+ sql
302
+ }) {
179
303
  // this function will exec <sql> in <db> and return <result>
180
- let bindByKey = !Array.isArray(bindList);
181
- let bindListLength = (
182
- Array.isArray(bindList)
183
- ? bindList.length
184
- : Object.keys(bindList).length
185
- );
186
- let result;
187
- let serialize = jsToSqlSerializer();
188
- if (tmpCsv || tmpRowList) {
189
- await dbTableInsertAsync({
190
- colList: tmpColList,
191
- colListPriority: tmpColListPriority,
192
- csv: tmpCsv,
304
+ let baton;
305
+ let bindByKey;
306
+ let bindListLength;
307
+ let externalbufferList;
308
+ let result;
309
+ while (modeRetry > 0) {
310
+ try {
311
+ return await dbExecAsync({
312
+ bindList,
193
313
  db,
194
- rowList: tmpRowList,
195
- tableName: tmpTableName
314
+ responseType,
315
+ sql
196
316
  });
197
- }
198
- Object.entries(bindList).forEach(function ([
199
- key, val
200
- ]) {
201
- if (bindByKey) {
202
- serialize(":" + key + "\u0000");
203
- }
204
- serialize(val);
205
- });
206
- result = await dbCallAsync("__dbExecAsync", db, [
207
- String(sql) + "\n;\nPRAGMA noop",
208
- bindListLength,
209
- serialize.bufResult,
210
- bindByKey,
211
- (
212
- responseType === "lastBlob"
213
- ? 1
214
- : responseType === "lastMatrixDouble"
215
- ? 2
216
- : 0
217
- )
218
- ].concat(serialize.bufSharedList));
219
- result = result[1];
220
- switch (responseType) {
221
- case "arraybuffer":
222
- case "lastBlob":
223
- return result;
224
- case "lastMatrixDouble":
225
- return new Float64Array(result);
226
- case "list":
227
- return JSON.parse(new TextDecoder().decode(result));
228
- default:
229
- result = JSON.parse(new TextDecoder().decode(result));
230
- return result.map(function (rowList) {
231
- let colList = rowList.shift();
232
- return rowList.map(function (row) {
233
- let dict = {};
234
- colList.forEach(function (key, ii) {
235
- dict[key] = row[ii];
236
- });
237
- return dict;
238
- });
317
+ } catch (err) {
318
+ assertOrThrow(modeRetry > 0, err);
319
+ consoleError(err);
320
+ consoleError(
321
+ "dbExecAsync - retry failed sql-query with "
322
+ + modeRetry
323
+ + " remaining retries"
324
+ );
325
+ modeRetry -= 1;
326
+ await new Promise(function (resolve) {
327
+ setTimeout(resolve, 50);
239
328
  });
240
329
  }
241
330
  }
242
-
243
- async function dbExecWithRetryAsync(option) {
244
- // this function will exec <sql> in <db> and return <result> with <retryLimit>
245
- let retry = option.retryLimit || 1;
246
- while (true) {
247
- try {
248
- return await dbExecAsync(option);
249
- } catch (err) {
250
- assertOrThrow(retry > 0, err);
251
- consoleError(err);
252
- consoleError(
253
- "dbExecWithRetryAsync - retry failed sql-query with "
254
- + retry
255
- + " remaining retry"
256
- );
257
- retry -= 1;
258
- }
331
+ baton = jsbatonCreate();
332
+ bindByKey = !Array.isArray(bindList);
333
+ bindListLength = (
334
+ Array.isArray(bindList)
335
+ ? bindList.length
336
+ : Object.keys(bindList).length
337
+ );
338
+ externalbufferList = [];
339
+ Object.entries(bindList).forEach(function ([
340
+ key, val
341
+ ]) {
342
+ if (bindByKey) {
343
+ baton = jsbatonValuePush({
344
+ baton,
345
+ value: ":" + key + "\u0000"
346
+ });
259
347
  }
260
- }
261
-
262
- function dbGetLastBlobAsync({
263
- bindList = [],
264
- db,
265
- sql
266
- }) {
267
- // this function will exec <sql> in <db> and return last value retrieved
268
- // from execution as raw blob/buffer
269
- return dbExecAsync({
270
- bindList,
271
- db,
272
- responseType: "lastBlob",
273
- sql
348
+ baton = jsbatonValuePush({
349
+ baton,
350
+ externalbufferList,
351
+ value: val
274
352
  });
275
- }
276
-
277
- function dbGetLastMatrixDouble({
278
- bindList = [],
279
- db,
280
- sql
281
- }) {
282
- // this function will exec <sql> in <db> and return last SELECT-statement
283
- // from execution as row x col matrix of doubles
284
- return dbExecAsync({
285
- bindList,
286
- db,
287
- responseType: "lastMatrixDouble",
288
- sql
353
+ });
354
+ result = await dbCallAsync(
355
+ baton,
356
+ "_dbExec",
357
+ db, // 0
358
+ String(sql) + "\n;\nPRAGMA noop", // 1
359
+ bindListLength, // 2
360
+ bindByKey, // 3
361
+ ( // 4
362
+ responseType === "lastBlob"
363
+ ? 1
364
+ : 0
365
+ ),
366
+ undefined, // 5
367
+ undefined, // 6
368
+ undefined, // 7 - response
369
+ ...externalbufferList // 8
370
+ );
371
+ result = result[2 + 7];
372
+ switch (responseType) {
373
+ case "arraybuffer":
374
+ case "lastBlob":
375
+ return result;
376
+ case "list":
377
+ return JSON.parse(new TextDecoder().decode(result));
378
+ default:
379
+ result = JSON.parse(new TextDecoder().decode(result));
380
+ return result.map(function (rowList) {
381
+ let colList = rowList.shift();
382
+ return rowList.map(function (row) {
383
+ let dict = {};
384
+ colList.forEach(function (key, ii) {
385
+ dict[key] = row[ii];
386
+ });
387
+ return dict;
388
+ });
289
389
  });
290
390
  }
391
+ }
291
392
 
292
- async function dbMemoryLoadAsync({
293
- db,
294
- filename
295
- }) {
296
- // This function will load <filename> to <db>
297
- assertOrThrow(filename, "invalid filename " + filename);
298
- await dbCallAsync("__dbMemoryLoadOrSave", db, [
299
- String(filename), 0
300
- ]);
393
+ async function dbFileExportAsync({
394
+ db,
395
+ dbData,
396
+ filename,
397
+ modeExport = 1
398
+ }) {
399
+ // This function will export <db> to <filename>
400
+ if (IS_BROWSER) {
401
+ filename = FILENAME_DBTMP;
301
402
  }
403
+ assertOrThrow(
404
+ typeof filename === "string" && filename,
405
+ `invalid filename ${filename}`
406
+ );
407
+ return await dbCallAsync(
408
+ undefined,
409
+ "_dbFileImportOrExport",
410
+ db, // 0. sqlite3 * pInMemory,
411
+ String(filename), // 1. char *zFilename,
412
+ modeExport, // 2. const int isSave
413
+ undefined, // 3. undefined
414
+ dbData // 4. dbData
415
+ );
416
+ }
302
417
 
303
- async function dbMemorySaveAsync({
418
+ async function dbFileImportAsync({
419
+ db,
420
+ dbData,
421
+ filename
422
+ }) {
423
+ // This function will import <filename> to <db>
424
+ await dbFileExportAsync({
304
425
  db,
305
- filename
306
- }) {
307
- // This function will save <db> to <filename>
308
- assertOrThrow(filename, "invalid filename " + filename);
309
- await dbCallAsync("__dbMemoryLoadOrSave", db, [
310
- String(filename), 1
311
- ]);
312
- }
313
-
314
- async function dbOpenAsync({
426
+ dbData,
315
427
  filename,
316
- flags,
317
- threadCount = 1
318
- }) {
428
+ modeExport: 0
429
+ });
430
+ }
431
+
432
+ async function dbNoopAsync(...argList) {
433
+ // this function will do nothing except return argList
434
+ return await cCallAsync(undefined, "_dbNoop", ...argList);
435
+ }
436
+
437
+ async function dbOpenAsync({
438
+ afterFinalization,
439
+ dbData,
440
+ filename,
441
+ flags,
442
+ rawPtr,
443
+ threadCount = 1
444
+ }) {
319
445
  // this function will open and return sqlite-database-connection <db>
320
446
  // int sqlite3_open_v2(
321
447
  // const char *filename, /* Database filename (UTF-8) */
@@ -323,1538 +449,496 @@ file sqlmath.js
323
449
  // int flags, /* Flags */
324
450
  // const char *zVfs /* Name of VFS module to use */
325
451
  // );
326
- let connPool;
327
- let db = {};
328
- assertOrThrow(
329
- typeof filename === "string",
330
- "invalid filename " + filename
331
- );
332
- connPool = await Promise.all(Array.from(new Array(
333
- threadCount
334
- ), async function () {
335
- let finalizer;
336
- let ptr = await cCall("__dbOpenAsync", [
337
- String(filename),
338
- undefined,
339
- flags ?? (
340
- SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI
341
- ),
342
- undefined
343
- ]);
344
- ptr = ptr[0][0];
345
- finalizer = new BigInt64Array(addon.__dbFinalizerCreate());
346
- finalizer[0] = BigInt(ptr);
347
- return finalizer;
348
- }));
349
- dbDict.set(db, {
350
- busy: 0,
351
- connPool,
352
- ii: 0,
353
- ptr: 0n
354
- });
355
- return db;
452
+ let connPool;
453
+ let db = {
454
+ filename
455
+ };
456
+ assertOrThrow(
457
+ typeof filename === "string",
458
+ `invalid filename ${filename}`
459
+ );
460
+ assertOrThrow(
461
+ !dbData || isExternalBuffer(dbData),
462
+ "dbData must be ArrayBuffer"
463
+ );
464
+ if (rawPtr) {
465
+ rawPtr = [
466
+ BigInt(rawPtr)
467
+ ];
468
+ threadCount = 1;
356
469
  }
357
-
358
- async function dbTableInsertAsync({
359
- colList,
360
- colListPriority,
361
- csv,
362
- db,
363
- rowList,
364
- tableName
365
- }) {
366
- // this function will create-or-replace temp <tablename> with <rowList>
367
- let serialize = jsToSqlSerializer();
368
- let sqlCreateTable;
369
- let sqlInsertRow;
370
- // normalize and validate tableName
371
- tableName = tableName || "__tmp1";
372
- tableName = "temp." + JSON.stringify(tableName.replace((
373
- /^temp\./
374
- ), ""));
375
- assertOrThrow((
376
- /^temp\."[A-Z_a-z][0-9A-Z_a-z]*?"$/
377
- ).test(tableName), "invalid tableName " + tableName);
378
- // parse csv
379
- if (!rowList && csv) {
380
- rowList = jsonRowListFromCsv({
381
- csv
382
- });
383
- }
384
- rowList = jsonRowListNormalize({
385
- colList,
386
- colListPriority,
387
- rowList
388
- });
389
- colList = rowList.shift();
390
- sqlCreateTable = (
391
- "DROP TABLE IF EXISTS " + tableName + ";"
392
- + "CREATE TEMP TABLE " + tableName + "(" + colList.join(",") + ");"
470
+ connPool = await Promise.all(Array.from(new Array(
471
+ threadCount
472
+ ), async function () {
473
+ let ptr = rawPtr || await cCallAsync(
474
+ undefined,
475
+ "_dbOpen",
476
+ // 0. const char *filename, Database filename (UTF-8)
477
+ String(filename),
478
+ // 1. sqlite3 **ppDb, OUT: SQLite db handle
479
+ undefined,
480
+ // 2. int flags, Flags
481
+ flags ?? (
482
+ SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI
483
+ ),
484
+ // 3. const char *zVfs Name of VFS module to use
485
+ undefined,
486
+ // 4. wasm-only - arraybuffer of raw sqlite-database to open in wasm
487
+ dbData
393
488
  );
394
- sqlInsertRow = (
395
- "INSERT INTO " + tableName + " VALUES("
396
- + ",?".repeat(colList.length).slice(1) + ");"
397
- );
398
- rowList.forEach(function (row) {
399
- row.forEach(serialize);
400
- });
401
- await dbCallAsync("__dbTableInsertAsync", db, [
402
- String(sqlCreateTable),
403
- String(sqlInsertRow),
404
- serialize.bufResult,
405
- colList.length,
406
- rowList.length
407
- ]);
408
- }
489
+ ptr = rawPtr || [
490
+ ptr[0].getBigInt64(4 + 4, true)
491
+ ];
492
+ dbFinalizationRegistry.register(db, {
493
+ afterFinalization,
494
+ ptr
495
+ });
496
+ return ptr;
497
+ }));
498
+ dbDict.set(db, {
499
+ busy: 0,
500
+ connPool,
501
+ filename,
502
+ ii: 0
503
+ });
504
+ return db;
505
+ }
409
506
 
410
- function jsToSqlSerializer() {
411
- // this function will return another function that serializes javascript <val>
412
- // to <bufResult> as sqlite-values
413
- let BIGINT64_MAX = 2n ** 63n - 1n;
414
- let BIGINT64_MIN = -(2n ** 63n - 1n);
415
- let SQLITE_DATATYPE_BLOB = 0x04;
416
- // let SQLITE_DATATYPE_BLOB_0 = 0x14;
417
- let SQLITE_DATATYPE_FLOAT = 0x02;
418
- // let SQLITE_DATATYPE_FLOAT_0 = 0x12;
419
- let SQLITE_DATATYPE_INTEGER = 0x01;
420
- let SQLITE_DATATYPE_INTEGER_0 = 0x11;
421
- let SQLITE_DATATYPE_INTEGER_1 = 0x21;
422
- let SQLITE_DATATYPE_NULL = 0x05;
423
- let SQLITE_DATATYPE_SHAREDARRAYBUFFER = -0x01;
424
- let SQLITE_DATATYPE_TEXT = 0x03;
425
- let SQLITE_DATATYPE_TEXT_0 = 0x13;
426
- let bufResult = new DataView(new ArrayBuffer(2048));
427
- let bufSharedList = [];
428
- let offset = 0;
429
- function bufferAppendDatatype(datatype, byteLength) {
430
- // this function will grow <bufResult> by <bytelength> and append <datatype>
431
- let nn = offset + 1 + byteLength;
432
- let tmp;
433
- // exponentially grow bufResult as needed
434
- if (bufResult.byteLength < nn) {
435
- assertOrThrow(nn <= SQLITE_MAX_LENGTH2, (
436
- "sqlite - string or blob exceeds size limit of "
437
- + SQLITE_MAX_LENGTH2 + " bytes"
438
- ));
439
- tmp = bufResult;
440
- bufResult = new DataView(new ArrayBuffer(
441
- Math.min(2 ** Math.ceil(Math.log2(nn)), SQLITE_MAX_LENGTH2)
442
- ));
443
- // copy tmp to bufResult with offset
444
- bufferSetBuffer(bufResult, tmp, 0);
445
- // save bufResult
446
- serialize.bufResult = bufResult;
447
- }
448
- bufResult.setUint8(offset, datatype);
449
- offset += 1;
450
- }
451
- function bufferSetBigint64(offset, val) {
452
- // this function will set bigint <val> to buffer <bufResult> at <offset>
453
- assertOrThrow(
454
- BIGINT64_MIN <= val && val <= BIGINT64_MAX,
455
- (
456
- "The value of \"value\" is out of range."
457
- + " It must be >= -(2n ** 63n) and < 2n ** 63n."
458
- )
459
- );
460
- bufResult.setBigInt64(offset, val, true);
461
- }
462
- function bufferSetBuffer(aa, bb, offset) {
463
- // this function will set buffer <bb> to buffer <aa> at <offset>
464
- aa = new Uint8Array(aa.buffer, aa.byteOffset, aa.byteLength);
465
- bb = new Uint8Array(bb.buffer, bb.byteOffset, bb.byteLength);
466
- aa.set(bb, offset);
467
- return bb.byteLength;
468
- }
469
- function serialize(val) {
470
- // this function will write to <bufResult>, <val> at given <offset>
507
+ function isExternalBuffer(buf) {
508
+ // this function will check if <buf> is ArrayBuffer or SharedArrayBuffer
509
+ return buf && (
510
+ buf.constructor === ArrayBuffer
511
+ || (
512
+ typeof SharedArrayBuffer === "function"
513
+ && buf.constructor === SharedArrayBuffer
514
+ )
515
+ );
516
+ }
517
+
518
+ function jsbatonCreate() {
519
+ // this function will create buffer <baton>
520
+ let baton = new DataView(new ArrayBuffer(1024));
521
+ // offset nalloc, nused
522
+ baton.setInt32(4, SQLITE_DATATYPE_OFFSET, true);
523
+ return baton;
524
+ }
525
+
526
+ function jsbatonValuePush({
527
+ argi,
528
+ baton,
529
+ externalbufferList,
530
+ value
531
+ }) {
532
+ // this function will push <value> to buffer <baton>
533
+ let nn;
534
+ let nused;
535
+ let tmp;
536
+ let vsize;
537
+ let vtype;
471
538
  /*
472
539
  #define SQLITE_DATATYPE_BLOB 0x04
473
- #define SQLITE_DATATYPE_BLOB_0 0x14
540
+ // #define SQLITE_DATATYPE_BLOB_0 0x14
474
541
  #define SQLITE_DATATYPE_FLOAT 0x02
475
- #define SQLITE_DATATYPE_FLOAT_0 0x12
542
+ // #define SQLITE_DATATYPE_FLOAT_0 0x12
476
543
  #define SQLITE_DATATYPE_INTEGER 0x01
477
544
  #define SQLITE_DATATYPE_INTEGER_0 0x11
478
545
  #define SQLITE_DATATYPE_INTEGER_1 0x21
479
546
  #define SQLITE_DATATYPE_NULL 0x05
547
+ #define SQLITE_DATATYPE_EXTERNALBUFFER -0x01
480
548
  #define SQLITE_DATATYPE_TEXT 0x03
481
549
  #define SQLITE_DATATYPE_TEXT_0 0x13
482
- // 1. false.bigint
483
- // 2. false.boolean
484
- // 3. false.function
485
- // 4. false.number
486
- // 5. false.object
487
- // 6. false.string
488
- // 7. false.symbol
489
- // 8. false.undefined
490
- // 11. true.bigint
491
- // 12. true.boolean
492
- // 13. true.function
493
- // 14. true.number
494
- // 15. true.object
495
- // 16. true.string
496
- // 17. true.symbol
497
- // 18. true.undefined
550
+ // 1. false.bigint
551
+ // 2. false.boolean
552
+ // 3. false.function
553
+ // 4. false.number
554
+ // 5. false.object
555
+ // 6. false.string
556
+ // 7. false.symbol
557
+ // 8. false.undefined
558
+ // 9. true.bigint
559
+ // 10. true.boolean
560
+ // 11. true.function
561
+ // 12. true.number
562
+ // 13. true.object
563
+ // 14. true.string
564
+ // 15. true.symbol
565
+ // 16. true.undefined
566
+ // 17. true.buffer
567
+ // 18. true.externalbuffer
498
568
  */
499
- // -1. SharedArrayBuffer
500
- if (val && val.constructor === SharedArrayBuffer) {
501
- assertOrThrow(
502
- bufSharedList.length <= 0.5 * JSBATON_ARGC,
503
- (
504
- "too many SharedArrayBuffer's " + bufSharedList.length
505
- + " > " + (0.5 * JSBATON_ARGC)
506
- )
507
- );
508
- bufferAppendDatatype(SQLITE_DATATYPE_SHAREDARRAYBUFFER, 0);
509
- bufSharedList.push(new DataView(val));
510
- return;
511
- }
512
- // 12. true.boolean
513
- if (val === 1 || val === 1n || val === true) {
514
- bufferAppendDatatype(SQLITE_DATATYPE_INTEGER_1, 0);
515
- return;
516
- }
517
- switch (Boolean(val) + "." + typeof(val)) {
518
- // 1. false.bigint
519
- case "false.bigint":
520
- // 2. false.boolean
521
- case "false.boolean":
522
- // 4. false.number
523
- case "false.number":
524
- bufferAppendDatatype(SQLITE_DATATYPE_INTEGER_0, 0);
525
- return;
526
- // 3. false.function
527
- // case "false.function":
528
- // 5. false.object
529
- case "false.object":
530
- // 7. false.symbol
531
- // case "false.symbol":
532
- // 8. false.undefined
533
- case "false.undefined":
534
- // 13. true.function
535
- case "true.function":
536
- // 17. true.symbol
537
- case "true.symbol":
538
- // 18. true.undefined
539
- // case "true.undefined":
540
- bufferAppendDatatype(SQLITE_DATATYPE_NULL, 0);
541
- return;
542
- // 6. false.string
543
- case "false.string":
544
- bufferAppendDatatype(SQLITE_DATATYPE_TEXT_0, 0);
545
- return;
546
- // 11. true.bigint
547
- case "true.bigint":
548
- bufferAppendDatatype(SQLITE_DATATYPE_INTEGER, 8);
549
- bufferSetBigint64(offset, val);
550
- offset += 8;
551
- return;
552
- // 14. true.number
553
- case "true.number":
554
- bufferAppendDatatype(SQLITE_DATATYPE_FLOAT, 8);
555
- bufResult.setFloat64(offset, val, true);
556
- offset += 8;
557
- return;
558
- // 16. true.string
559
- case "true.string":
560
- val = new TextEncoder().encode(val);
561
- bufferAppendDatatype(SQLITE_DATATYPE_TEXT, 8 + val.byteLength);
562
- bufferSetBigint64(offset, BigInt(val.byteLength));
563
- offset += 8;
564
- offset += bufferSetBuffer(bufResult, val, offset);
565
- return;
566
- // 15. true.object
567
- default:
568
- assertOrThrow(
569
- val && typeof val === "object",
570
- "invalid data " + (typeof val) + " " + val
571
- );
572
- // write buffer
573
- if (ArrayBuffer.isView(val)) {
574
- if (val.byteLength === 0) {
575
- bufferAppendDatatype(SQLITE_DATATYPE_NULL, 0);
576
- return;
577
- }
578
- bufferAppendDatatype(
579
- SQLITE_DATATYPE_BLOB,
580
- 8 + val.byteLength
581
- );
582
- bufferSetBigint64(offset, BigInt(val.byteLength));
583
- offset += 8;
584
- // copy val to bufResult with offset
585
- bufferSetBuffer(bufResult, val, offset);
586
- offset += val.byteLength;
587
- return;
588
- }
589
- // write JSON.stringify(val)
590
- val = String(
591
- typeof val.toJSON === "function"
592
- ? val.toJSON()
593
- : JSON.stringify(val)
594
- );
595
- val = new TextEncoder().encode(val);
596
- bufferAppendDatatype(SQLITE_DATATYPE_TEXT, 8 + val.byteLength);
597
- bufferSetBigint64(offset, BigInt(val.byteLength));
598
- offset += 8;
599
- offset += bufferSetBuffer(bufResult, val, offset);
600
- }
601
- }
602
- // save bufResult
603
- serialize.bufResult = bufResult;
604
- // save bufSharedList
605
- serialize.bufSharedList = bufSharedList;
606
- return serialize;
569
+ // 10. true.boolean
570
+ if (value === 1 || value === 1n) {
571
+ value = true;
607
572
  }
608
-
609
- function jsonRowListFromCsv({
610
- csv
611
- }) {
612
- // this function will convert <csv>-text to json list-of-list
613
- /*
614
- https://tools.ietf.org/html/rfc4180#section-2
615
- Definition of the CSV Format
616
- While there are various specifications and implementations for the
617
- CSV format (for ex. [4], [5], [6] and [7]), there is no formal
618
- specification in existence, which allows for a wide variety of
619
- interpretations of CSV files. This section documents the format that
620
- seems to be followed by most implementations:
621
- 1. Each record is located on a separate line, delimited by a line
622
- break (CRLF). For example:
623
- aaa,bbb,ccc CRLF
624
- zzz,yyy,xxx CRLF
625
- 2. The last record in the file may or may not have an ending line
626
- break. For example:
627
- aaa,bbb,ccc CRLF
628
- zzz,yyy,xxx
629
- 3. There maybe an optional header line appearing as the first line
630
- of the file with the same format as normal record lines. This
631
- header will contain names corresponding to the fields in the file
632
- and should contain the same number of fields as the records in
633
- the rest of the file (the presence or absence of the header line
634
- should be indicated via the optional "header" parameter of this
635
- MIME type). For example:
636
- field_name,field_name,field_name CRLF
637
- aaa,bbb,ccc CRLF
638
- zzz,yyy,xxx CRLF
639
- 4. Within the header and each record, there may be one or more
640
- fields, separated by commas. Each line should contain the same
641
- number of fields throughout the file. Spaces are considered part
642
- of a field and should not be ignored. The last field in the
643
- record must not be followed by a comma. For example:
644
- aaa,bbb,ccc
645
- 5. Each field may or may not be enclosed in double quotes (however
646
- some programs, such as Microsoft Excel, do not use double quotes
647
- at all). If fields are not enclosed with double quotes, then
648
- double quotes may not appear inside the fields. For example:
649
- "aaa","bbb","ccc" CRLF
650
- zzz,yyy,xxx
651
- 6. Fields containing line breaks (CRLF), double quotes, and commas
652
- should be enclosed in double-quotes. For example:
653
- "aaa","b CRLF
654
- bb","ccc" CRLF
655
- zzz,yyy,xxx
656
- 7. If double-quotes are used to enclose fields, then a double-quote
657
- appearing inside a field must be escaped by preceding it with
658
- another double quote. For example:
659
- "aaa","b""bb","ccc"
660
- */
661
- let match;
662
- let quote = false;
663
- let rgx = (
664
- /(.*?)(""|"|,|\n)/g
665
- );
666
- let row = [];
667
- let rowList = [];
668
- let val = "";
669
- // normalize "\r\n" to "\n"
670
- csv = csv.replace((
671
- /\r\n?/g
672
- ), "\n");
673
- /*
674
- 2. The last record in the file may or may not have an ending line
675
- break. For example:
676
- aaa,bbb,ccc CRLF
677
- zzz,yyy,xxx
678
- */
679
- if (csv[csv.length - 1] !== "\n") {
680
- csv += "\n";
573
+ switch (Boolean(value) + "." + typeof(value)) {
574
+ // 1. false.bigint
575
+ case "false.bigint":
576
+ // 2. false.boolean
577
+ case "false.boolean":
578
+ // 4. false.number
579
+ case "false.number":
580
+ vtype = SQLITE_DATATYPE_INTEGER_0;
581
+ vsize = 0;
582
+ break;
583
+ // 3. false.function
584
+ // case "false.function":
585
+ // 5. false.object
586
+ case "false.object":
587
+ // 7. false.symbol
588
+ case "false.symbol":
589
+ // 8. false.undefined
590
+ case "false.undefined":
591
+ // 11. true.function
592
+ case "true.function":
593
+ // 15. true.symbol
594
+ case "true.symbol":
595
+ // 16. true.undefined
596
+ // case "true.undefined":
597
+ vtype = SQLITE_DATATYPE_NULL;
598
+ vsize = 0;
599
+ break;
600
+ // 6. false.string
601
+ case "false.string":
602
+ vtype = SQLITE_DATATYPE_TEXT_0;
603
+ vsize = 0;
604
+ break;
605
+ // 9. true.bigint
606
+ case "true.bigint":
607
+ vtype = SQLITE_DATATYPE_INTEGER;
608
+ vsize = 8;
609
+ break;
610
+ // 10. true.boolean
611
+ case "true.boolean":
612
+ vtype = SQLITE_DATATYPE_INTEGER_1;
613
+ vsize = 0;
614
+ break;
615
+ // 12. true.number
616
+ case "true.number":
617
+ vtype = SQLITE_DATATYPE_FLOAT;
618
+ vsize = 8;
619
+ break;
620
+ // 13. true.object
621
+ // 14. true.string
622
+ default:
623
+ // 18. true.externalbuffer
624
+ if (isExternalBuffer(value)) {
625
+ assertOrThrow(
626
+ !IS_BROWSER,
627
+ "external ArrayBuffer cannot be passed directly to wasm"
628
+ );
629
+ assertOrThrow(
630
+ externalbufferList.length <= 8,
631
+ "externalbufferList.length must be less than 8"
632
+ );
633
+ externalbufferList.push(new DataView(value));
634
+ vtype = SQLITE_DATATYPE_EXTERNALBUFFER;
635
+ vsize = 4;
636
+ break;
681
637
  }
682
- while (true) {
683
- match = rgx.exec(csv);
684
- if (!match) {
685
- return rowList;
686
- }
687
- // build val
688
- val += match[1];
689
- match = match[2];
690
- switch (quote + "." + match) {
691
- case "false.,":
692
- /*
693
- 4. Within the header and each record, there may be one or more
694
- fields, separated by commas. Each line should contain the same
695
- number of fields throughout the file. Spaces are considered part
696
- of a field and should not be ignored. The last field in the
697
- record must not be followed by a comma. For example:
698
- aaa,bbb,ccc
699
- */
700
- // delimit val
701
- row.push(val);
702
- val = "";
638
+ // 17. true.buffer
639
+ if (ArrayBuffer.isView(value)) {
640
+ if (value.byteLength === 0) {
641
+ vtype = SQLITE_DATATYPE_NULL;
642
+ vsize = 0;
703
643
  break;
704
- case "false.\"":
705
- case "true.\"":
706
- /*
707
- 5. Each field may or may not be enclosed in double quotes (however
708
- some programs, such as Microsoft Excel, do not use double quotes
709
- at all). If fields are not enclosed with double quotes, then
710
- double quotes may not appear inside the fields. For example:
711
- "aaa","bbb","ccc" CRLF
712
- zzz,yyy,xxx
713
- */
714
- assertOrThrow(quote || val === "", (
715
- "invalid csv - naked double-quote in unquoted-string "
716
- + JSON.stringify(val + "\"")
717
- ));
718
- quote = !quote;
719
- break;
720
- // backtrack for naked-double-double-quote
721
- case "false.\"\"":
722
- quote = true;
723
- rgx.lastIndex -= 1;
724
- break;
725
- case "false.\n":
726
- case "false.\r\n":
727
- /*
728
- 1. Each record is located on a separate line, delimited by a line
729
- break (CRLF). For example:
730
- aaa,bbb,ccc CRLF
731
- zzz,yyy,xxx CRLF
732
- */
733
- // delimit val
734
- row.push(val);
735
- val = "";
736
- // append row
737
- rowList.push(row);
738
- // reset row
739
- row = [];
740
- break;
741
- case "true.\"\"":
742
- /*
743
- 7. If double-quotes are used to enclose fields, then a double-quote
744
- appearing inside a field must be escaped by preceding it with
745
- another double quote. For example:
746
- "aaa","b""bb","ccc"
747
- */
748
- val += "\"";
749
- break;
750
- default:
751
- /*
752
- 6. Fields containing line breaks (CRLF), double quotes, and commas
753
- should be enclosed in double-quotes. For example:
754
- "aaa","b CRLF
755
- bb","ccc" CRLF
756
- zzz,yyy,xxx
757
- */
758
- assertOrThrow(quote, (
759
- "invalid csv - illegal character in unquoted-string "
760
- + JSON.stringify(match)
761
- ));
762
- val += match;
763
644
  }
645
+ vtype = SQLITE_DATATYPE_BLOB;
646
+ vsize = 4 + value.byteLength;
647
+ break;
764
648
  }
649
+ // 13. true.object
650
+ value = String(
651
+ typeof value === "string"
652
+ ? value
653
+ : typeof value.toJSON === "function"
654
+ ? value.toJSON()
655
+ : JSON.stringify(value)
656
+ );
657
+ // 14. true.string
658
+ value = new TextEncoder().encode(value);
659
+ vtype = SQLITE_DATATYPE_TEXT;
660
+ vsize = 4 + value.byteLength;
765
661
  }
766
-
767
- function jsonRowListNormalize({
768
- colList,
769
- colListPriority,
770
- rowList
771
- }) {
772
- // this function will normalize <rowList> with given <colList>
773
- let colDict = {};
774
- if (!(rowList?.length > 0)) {
775
- throw new Error("invalid rowList " + JSON.stringify(rowList));
776
- }
777
- // convert list-of-dict to list-of-list
778
- if (!Array.isArray(rowList[0])) {
779
- colList = new Map(Array.from(
780
- colList || []
781
- ).map(function (key, ii) {
782
- return [
783
- key, ii
784
- ];
785
- }));
786
- rowList = rowList.map(function (row) {
787
- Object.keys(row).forEach(function (key) {
788
- if (!colList.has(key)) {
789
- colList.set(key, colList.size);
790
- }
791
- });
792
- return Array.from(colList.keys()).map(function (key) {
793
- return row[key];
794
- });
795
- });
796
- colList = Array.from(colList.keys());
797
- }
798
- if (!colList) {
799
- colList = rowList[0];
800
- rowList = rowList.slice(1);
801
- }
802
- if (!(colList?.length > 0)) {
803
- throw new Error("invalid colList " + JSON.stringify(colList));
804
- }
805
- colList = colList.map(function (key) {
806
- // sanitize column-name
807
- key = String(key).replace((
808
- /^[^A-Z_a-z]/
809
- ), "c_" + key);
810
- key = key.replace((
811
- /[^0-9A-Z_a-z]/g
812
- ), "_");
813
- // for duplicate column-name, add ordinal _2, _3, _4, ...
814
- colDict[key] = colDict[key] || 0;
815
- colDict[key] += 1;
816
- if (colDict[key] > 1) {
817
- key += "_" + colDict[key];
818
- }
819
- return key;
820
- });
821
- // normalize rowList
822
- rowList = rowList.map(function (row) {
823
- return (
824
- row.length === colList.length
825
- ? row
826
- : colList.map(function (ignore, ii) {
827
- return row[ii];
828
- })
829
- );
830
- });
831
- if (!colListPriority) {
832
- rowList.unshift(colList);
833
- return rowList;
662
+ nused = baton.getInt32(4, true);
663
+ nn = nused + 1 + vsize;
664
+ assertOrThrow(
665
+ nn <= 0xffff_ffff,
666
+ "jsbaton cannot exceed 0x7fff_ffff / 2,147,483,647 bytes"
667
+ );
668
+ // exponentially grow baton as needed
669
+ if (baton.byteLength < nn) {
670
+ tmp = baton;
671
+ baton = new DataView(new ArrayBuffer(
672
+ Math.min(2 ** Math.ceil(Math.log2(nn)), 0x7fff_ffff)
673
+ ));
674
+ // update nalloc
675
+ baton.setInt32(0, baton.byteLength, true);
676
+ // copy tmp to baton
677
+ new Uint8Array(
678
+ baton.buffer,
679
+ baton.byteOffset,
680
+ nused
681
+ ).set(new Uint8Array(tmp.buffer, tmp.byteOffset, nused), 0);
682
+ }
683
+ // push vtype
684
+ baton.setUint8(nused, vtype);
685
+ // update nused
686
+ baton.setInt32(4, nused + 1 + vsize, true);
687
+ // handle blob-value
688
+ switch (vtype) {
689
+ case SQLITE_DATATYPE_BLOB:
690
+ case SQLITE_DATATYPE_TEXT:
691
+ // set argv[ii] to blob/text location
692
+ if (argi !== undefined) {
693
+ baton.setInt32(8 + argi * 8, nused, true);
834
694
  }
835
- // sort colList by colListPriority
836
- colListPriority = new Map([].concat(
837
- colListPriority,
838
- colList
839
- ).map(function (key) {
840
- return [
841
- key, colList.indexOf(key)
842
- ];
843
- }).filter(function ([
844
- ignore, ii
845
- ]) {
846
- return ii >= 0;
847
- }));
848
- colList = Array.from(colListPriority.keys());
849
- colListPriority = Array.from(colListPriority.values());
850
- rowList = rowList.map(function (row) {
851
- return colListPriority.map(function (ii) {
852
- return row[ii];
853
- });
854
- });
855
- rowList.unshift(colList);
856
- return rowList;
695
+ vsize -= 4;
696
+ // push vsize
697
+ assertOrThrow(
698
+ 0 <= vsize && vsize <= 1_000_000_000,
699
+ (
700
+ "sqlite-blob byte-length must be within inclusive-range"
701
+ + " 0 to 1,000,000,000"
702
+ )
703
+ );
704
+ baton.setInt32(nused + 1, vsize, true);
705
+ new Uint8Array(
706
+ baton.buffer,
707
+ nused + 1 + 4,
708
+ vsize
709
+ ).set(new Uint8Array(value.buffer, value.byteOffset, vsize), 0);
710
+ break;
711
+ case SQLITE_DATATYPE_EXTERNALBUFFER:
712
+ vsize = value.byteLength;
713
+ // push vsize
714
+ assertOrThrow(
715
+ 0 <= vsize && vsize <= 1_000_000_000,
716
+ (
717
+ "sqlite-blob byte-length must be within inclusive-range"
718
+ + " 0 to 1,000,000,000"
719
+ )
720
+ );
721
+ baton.setInt32(nused + 1, vsize, true);
722
+ break;
723
+ case SQLITE_DATATYPE_FLOAT:
724
+ baton.setFloat64(nused + 1, value, true);
725
+ break;
726
+ case SQLITE_DATATYPE_INTEGER:
727
+ assertOrThrow(
728
+ (
729
+ -9_223_372_036_854_775_808n <= value
730
+ && value <= 9_223_372_036_854_775_807n
731
+ ),
732
+ (
733
+ "sqlite-integer must be within inclusive-range "
734
+ + "-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807"
735
+ )
736
+ );
737
+ baton.setBigInt64(nused + 1, value, true);
738
+ break;
857
739
  }
740
+ return baton;
741
+ }
858
742
 
859
- function testAll() {
860
- // this function will run all tests
861
- testList.forEach(function (testFunc) {
862
- testFunc();
863
- });
864
- }
743
+ function jsbatonValueString({
744
+ argi,
745
+ baton
746
+ }) {
747
+ // this function will return string-value from <baton> at given <offset>
748
+ let offset = baton.getInt32(4 + 4 + argi * 8, true);
749
+ return new TextDecoder().decode(new Uint8Array(
750
+ baton.buffer,
751
+ offset + 1 + 4,
752
+ // remove null-terminator from string
753
+ baton.getInt32(offset + 1, true) - 1
754
+ ));
755
+ }
865
756
 
866
- function testAssertXxx() {
867
- // this function will test assertXxx's handling-behavior
868
- // test assertNumericalEqual's handling-behavior
869
- assertNumericalEqual(1, 1);
870
- assertErrorThrownAsync(function () {
871
- assertNumericalEqual(0, 0);
872
- }, "value cannot be 0 or falsy");
873
- assertErrorThrownAsync(function () {
874
- assertNumericalEqual(1, 2);
875
- }, "1 != 2");
876
- assertErrorThrownAsync(function () {
877
- assertNumericalEqual(1, 2, "aa");
878
- }, "aa");
879
- }
757
+ function noop(val) {
880
758
 
881
- function testCcall() {
882
- // this function will test cCall's handling-behavior
883
- [
884
- [-0, "0"],
885
- [-Infinity, "0"],
886
- [0, "0"],
887
- [1 / 0, "0"],
888
- [Infinity, "0"],
889
- [false, "0"],
890
- [null, "0"],
891
- [true, "1"],
892
- [undefined, "0"],
893
- [{}, "0"]
894
- ].forEach(async function ([
895
- aa, bb
896
- ]) {
897
- let cc;
898
- cc = String(
899
- await cCall("noopAsync", [
900
- aa
901
- ])
902
- )[0][0];
903
- assertOrThrow(bb === cc, [aa, bb, cc]);
904
- cc = String(cCall("noopSync", [
905
- aa
906
- ]))[0][0];
907
- assertOrThrow(bb === cc, [aa, bb, cc]);
908
- });
909
- }
759
+ // This function will do nothing except return <val>.
910
760
 
911
- async function testDbBind() {
912
- // this function will test db's bind handling-behavior
913
- let db = await dbOpenAsync({
914
- filename: ":memory:"
915
- });
916
- async function testDbGetLastBlobAsync(val) {
917
- return await dbGetLastBlobAsync({
918
- bindList: [
919
- val
920
- ],
921
- db,
922
- sql: "SELECT 1, 2, 3; SELECT 1, 2, ?"
923
- });
924
- }
925
- // test bigint-error handling-behavior
926
- noop([
927
- -(2n ** 63n),
928
- 2n ** 63n
929
- ]).forEach(function (val) {
930
- assertErrorThrownAsync(testDbGetLastBlobAsync.bind(undefined, val));
931
- });
932
- // test datatype handling-behavior
933
- [
934
- // -1. SharedArrayBuffer
935
- // new SharedArrayBuffer(0), null,
936
- // 1. bigint
937
- -0n, -0,
938
- -0x7fffffffffffffffn, "-9223372036854775807",
939
- -1n, -1,
940
- -2n, -2,
941
- 0n, 0,
942
- 0x7fffffffffffffffn, "9223372036854775807",
943
- 1n, 1,
944
- 2n, 2,
945
- // 2. boolean
946
- false, 0,
947
- true, 1,
948
- // 3. function
949
- noop, null,
950
- // 4. number
951
- -0, 0,
952
- -1 / 0, null,
953
- -1e-999, 0,
954
- -1e999, null,
955
- -2, -2,
956
- -Infinity, null,
957
- -NaN, 0,
958
- 0, 0,
959
- 1 / 0, null,
960
- 1e-999, 0,
961
- 1e999, null,
962
- 2, 2,
963
- Infinity, null,
964
- NaN, 0,
965
- // 5. object
966
- new Uint8Array(0), null,
967
- new TextEncoder().encode(""), null,
968
- new TextEncoder().encode("\u0000"), null,
969
- new TextEncoder().encode("\u0000\u{1f600}\u0000"), null,
970
- [], "[]",
971
- new Date(0), "1970-01-01T00:00:00.000Z",
972
- new RegExp(), "{}",
973
- null, null,
974
- {}, "{}",
975
- // 6. string
976
- "", "",
977
- "0", "0",
978
- "1", "1",
979
- "2", "2",
980
- "\u0000", "\u0000",
981
- "\u0000\u{1f600}\u0000", "\u0000\u{1f600}\u0000",
982
- "a".repeat(9999), "a".repeat(9999),
983
- // 7. symbol
984
- Symbol(), null,
985
- // 8. undefined
986
- undefined, null
987
- ].forEach(function (aa, ii, list) {
988
- let bb = list[ii + 1];
989
- if (ii % 2 === 1) {
990
- return;
991
- }
992
- ii *= 0.5;
993
- // test dbGetLastBlobAsync's bind handling-behavior
994
- [
995
- aa
996
- ].forEach(async function (aa) {
997
- let cc = String(bb);
998
- let dd = new TextDecoder().decode(
999
- await testDbGetLastBlobAsync(aa)
1000
- );
1001
- switch (typeof(aa)) {
1002
- case "bigint":
1003
- aa = Number(aa);
1004
- break;
1005
- case "function":
1006
- case "symbol":
1007
- case "undefined":
1008
- cc = "";
1009
- break;
1010
- case "number":
1011
- switch (aa) {
1012
- case -2:
1013
- cc = "-2.0";
1014
- break;
1015
- case -Infinity:
1016
- cc = "-Inf";
1017
- break;
1018
- case 2:
1019
- cc = "2.0";
1020
- break;
1021
- case Infinity:
1022
- cc = "Inf";
1023
- break;
1024
- }
1025
- break;
1026
- case "object":
1027
- if (ArrayBuffer.isView(aa)) {
1028
- cc = new TextDecoder().decode(aa);
1029
- break;
1030
- }
1031
- if (aa === null) {
1032
- cc = "";
1033
- }
1034
- break;
1035
- }
1036
- // debugInline(ii, aa, bb, cc, dd);
1037
- assertJsonEqual(cc, dd, {
1038
- ii,
1039
- aa, //jslint-quiet
1040
- bb,
1041
- cc,
1042
- dd
1043
- });
1044
- });
1045
- // test dbGetLastMatrixDouble's bind handling-behavior
1046
- [
1047
- aa
1048
- ].forEach(async function (aa) {
1049
- let cc = Number(
1050
- typeof aa === "symbol"
1051
- ? 0
1052
- : aa
1053
- ) || 0;
1054
- let dd = await dbGetLastMatrixDouble({
1055
- bindList: [
1056
- aa
1057
- ],
1058
- db,
1059
- sql: "SELECT 1, 2, 3; SELECT 1, 2, ?"
1060
- });
1061
- switch (typeof(aa)) {
1062
- case "bigint":
1063
- aa = String(aa);
1064
- break;
1065
- case "object":
1066
- if (typeof aa?.getUTCFullYear === "function") {
1067
- cc = aa.getUTCFullYear();
1068
- }
1069
- break;
1070
- }
1071
- cc = new Float64Array([
1072
- 1, 3, 1, 2, cc
1073
- ]);
1074
- // debugInline(ii, aa, bb, cc, dd);
1075
- cc.forEach(function (val, jj) {
1076
- assertJsonEqual(
1077
- val,
1078
- dd[jj],
1079
- {
1080
- ii,
1081
- aa, //jslint-quiet
1082
- bb,
1083
- cc,
1084
- dd
1085
- }
1086
- );
1087
- });
1088
- });
1089
- // test dbExecAsync's responseType handling-behavior
1090
- [
1091
- "arraybuffer",
1092
- "list",
1093
- undefined
1094
- ].forEach(async function (responseType) {
1095
- let cc = noop(
1096
- await dbExecAsync({
1097
- bindList: [
1098
- aa
1099
- ],
1100
- db,
1101
- responseType,
1102
- sql: "SELECT ? AS val"
1103
- })
1104
- );
1105
- // debugInline(ii, responseType, aa, bb, cc);
1106
- switch (responseType) {
1107
- case "arraybuffer":
1108
- cc = JSON.parse(new TextDecoder().decode(cc))[0][1][0];
1109
- break;
1110
- case "list":
1111
- cc = cc[0][1][0];
1112
- break;
1113
- default:
1114
- cc = cc[0][0].val;
1115
- }
1116
- assertJsonEqual(bb, cc, {
1117
- aa,
1118
- bb,
1119
- cc
1120
- });
1121
- });
1122
- // test dbExecAsync's bind handling-behavior
1123
- [
1124
- [
1125
- [
1126
- bb, bb, 0
1127
- ],
1128
- (
1129
- "SELECT 0;"
1130
- + " SELECT ? AS c1, ? AS c2, ? AS c3, ? AS c4"
1131
- + " UNION ALL SELECT ?1, ?2, ?3, ?4"
1132
- + " UNION ALL SELECT ?1, ?2, ?3, ?4"
1133
- )
1134
- ], [
1135
- {
1136
- k1: bb,
1137
- k2: bb,
1138
- k3: 0
1139
- },
1140
- (
1141
- "SELECT 0;"
1142
- + " SELECT $k1 AS c1, $k2 AS c2, $k3 AS c3, $k4 AS c4"
1143
- + " UNION ALL SELECT :k1, :k2, :k3, :k4"
1144
- + " UNION ALL SELECT @k1, @k2, @k3, @k4"
1145
- )
1146
- ]
1147
- ].forEach(async function ([
1148
- bindList, sql
1149
- ]) {
1150
- let cc = noop(
1151
- await dbExecAsync({
1152
- bindList,
1153
- db,
1154
- responseType: "list",
1155
- sql
1156
- })
1157
- );
1158
- // debugInline(ii, aa, bb, cc);
1159
- assertJsonEqual(
1160
- [
1161
- [
1162
- [
1163
- "0"
1164
- ], [
1165
- 0
1166
- ]
1167
- ], [
1168
- [
1169
- "c1", "c2", "c3", "c4"
1170
- ], [
1171
- bb, bb, 0, undefined
1172
- ], [
1173
- bb, bb, 0, undefined
1174
- ], [
1175
- bb, bb, 0, undefined
1176
- ]
1177
- ]
1178
- ],
1179
- cc
1180
- );
1181
- });
1182
- // test dbTableInsertAsync's bind handling-behavior
1183
- [
1184
- {
1185
- // test list-of-list handling-behavior
1186
- rowList: [
1187
- [
1188
- "c1", "c2", "c3"
1189
- ],
1190
- [
1191
- aa, aa
1192
- ]
1193
- ]
1194
- }, {
1195
- // test list-of-dict handling-behavior
1196
- rowList: [
1197
- {
1198
- "c1": aa,
1199
- "c2": aa,
1200
- "c3": undefined
1201
- }
1202
- ]
1203
- }, {
1204
- // test colList and list-of-list handling-behavior
1205
- colList: [
1206
- "c1", "c2", "c3"
1207
- ],
1208
- rowList: [
1209
- [
1210
- aa, aa
1211
- ]
1212
- ]
1213
- }, {
1214
- // test colList and list-of-dict handling-behavior
1215
- colList: [
1216
- "c1", "c2", "c3"
1217
- ],
1218
- rowList: [
1219
- {
1220
- "c1": aa,
1221
- "c2": aa,
1222
- "c3": undefined
1223
- }
1224
- ]
1225
- }, {
1226
- // test colList and list-of-list handling-behavior
1227
- colList: [
1228
- "c1", "c3", "c2"
1229
- ],
1230
- colListPriority: [
1231
- "c1", "c2"
1232
- ],
1233
- rowList: [
1234
- [
1235
- aa, undefined, aa
1236
- ]
1237
- ]
1238
- }
1239
- ].forEach(async function ({
1240
- colList,
1241
- colListPriority,
1242
- rowList
1243
- }, jj) {
1244
- let cc = noop(
1245
- await dbExecAsync({
1246
- db,
1247
- responseType: "list",
1248
- sql: `SELECT * FROM datatype_${ii}_${jj}`,
1249
- tmpColList: colList,
1250
- tmpColListPriority: colListPriority,
1251
- tmpRowList: rowList,
1252
- tmpTableName: "datatype_" + ii + "_" + jj
1253
- })
1254
- )[0];
1255
- // debugInline(ii, jj, aa, bb, cc);
1256
- assertJsonEqual([
1257
- [
1258
- "c1", "c2", "c3"
1259
- ], [
1260
- bb, bb, undefined
1261
- ]
1262
- ], cc);
1263
- });
1264
- });
1265
- }
761
+ return val;
762
+ }
1266
763
 
1267
- async function testDbCloseAsync() {
1268
- // this function will test dbCloseAsync's handling-behavior
1269
- let db = await dbOpenAsync({
1270
- filename: ":memory:"
1271
- });
1272
- // test null-case handling-behavior
1273
- assertErrorThrownAsync(function () {
1274
- return dbCloseAsync({});
1275
- }, "invalid or closed db");
1276
- // test close handling-behavior
1277
- await dbCloseAsync({
1278
- db
1279
- });
1280
- }
764
+ function objectDeepCopyWithKeysSorted(obj) {
1281
765
 
1282
- async function testDbExecAsync() {
1283
- // this function will test dbExecAsync's handling-behavior
1284
- let db = await dbOpenAsync({
1285
- filename: ":memory:"
1286
- });
1287
- // test null-case handling-behavior
1288
- assertErrorThrownAsync(function () {
1289
- return dbExecAsync({
1290
- db,
1291
- sql: undefined
1292
- });
1293
- }, "near \"undefined\": syntax error");
1294
- // test race-condition handling-behavior
1295
- Array.from(new Array(4)).forEach(async function () {
1296
- let result;
1297
- try {
1298
- result = JSON.stringify(
1299
- await dbExecAsync({
1300
- bindList: [
1301
- new TextEncoder().encode("foob"),
1302
- new TextEncoder().encode("fooba"),
1303
- new TextEncoder().encode("foobar")
1304
- ],
1305
- db,
1306
- responseType: "list",
1307
- sql: (`
1308
- CREATE TABLE testDbExecAsync1 AS
1309
- SELECT 101 AS c101, 102 AS c102
1310
- --
1311
- UNION ALL
1312
- VALUES
1313
- (201, 202),
1314
- (301, NULL);
1315
- CREATE TABLE testDbExecAsync2 AS
1316
- SELECT 401 AS c401, 402 AS c402, 403 AS c403
1317
- --
1318
- UNION ALL
1319
- VALUES
1320
- (501, 502.0123, 5030123456789),
1321
- (601, '602', '603_\"\x01\x08\x09\x0a\x0b\x0c\x0d\x0e'),
1322
- (?1, ?2, ?3),
1323
- (tostring(?1), tostring(?2), tostring(?3)),
1324
- (tobase64(?1), tobase64(?2), tobase64(?3)),
1325
- (
1326
- tobase64(uncompress(compress(?1))),
1327
- tobase64(uncompress(compress(?2))),
1328
- tobase64(uncompress(compress(?3)))
1329
- );
1330
- SELECT * FROM testDbExecAsync1;
1331
- SELECT * FROM testDbExecAsync2;
1332
- `)
1333
- })
1334
- );
1335
- assertJsonEqual(result, JSON.stringify([
1336
- [
1337
- ["c101", "c102"],
1338
- [101, 102],
1339
- [201, 202],
1340
- [301, null]
1341
- ],
1342
- [
1343
- ["c401", "c402", "c403"],
1344
- [401, 402, 403],
1345
- [501, 502.0123, 5030123456789],
1346
- [601, "602", "603_\"\u0001\b\t\n\u000b\f\r\u000e"],
1347
- [
1348
- null, null, null
1349
- ],
1350
- [
1351
- "foob", "fooba", "foobar"
1352
- ],
1353
- [
1354
- "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"
1355
- ],
1356
- [
1357
- "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"
1358
- ]
1359
- ]
1360
- ]));
1361
- } catch (err) {
1362
- assertOrThrow(
1363
- err.message.indexOf(
1364
- "table testDbExecAsync1 already exists"
1365
- ) >= 0,
1366
- err
1367
- );
1368
- }
1369
- });
1370
- // test close-while-busy handling-behavior
1371
- assertErrorThrownAsync(function () {
1372
- return dbCloseAsync({
1373
- db
1374
- });
1375
- }, (
1376
- /db cannot close with \d+? actions pending/
1377
- ));
1378
- // test sqlmath-defined-func handling-behavior
1379
- [
1380
- "COT(NULL)", null,
1381
- "COT('-1')", -0.642092615934331,
1382
- "COT('0')", null,
1383
- "COT('1')", 0.642092615934331,
1384
- "COT(-1)", -0.642092615934331,
1385
- "COT(0)", null,
1386
- "COT(1)", 0.642092615934331,
1387
- "COTH(NULL)", null,
1388
- "COTH('-1')", -1.31303528549933,
1389
- "COTH('0')", null,
1390
- "COTH('1')", 1.31303528549933,
1391
- "COTH(-1)", -1.31303528549933,
1392
- "COTH(0)", null,
1393
- "COTH(1)", 1.31303528549933,
1394
- "ROUNDORZERO(NULL)", 0,
1395
- "ROUNDORZERO(NULL, NULL)", 0,
1396
- "ROUNDORZERO(NULL, 0.5)", 0,
1397
- "ROUNDORZERO(0.5, NULL)", 1,
1398
- "ROUNDORZERO(0.5, 0.5)", 1,
1399
- "ROUNDORZERO(0.5, 1)", 0.5,
1400
- "SIGN(NULL)", null,
1401
- "SIGN('-1')", -1,
1402
- "SIGN('0')", 0,
1403
- "SIGN('1')", 1,
1404
- "SIGN(-0x7fffffffffffffff)", -1,
1405
- "SIGN(-1)", -1,
1406
- "SIGN(-1e999)", -1,
1407
- "SIGN(0)", 0,
1408
- "SIGN(0x7fffffffffffffff)", 1,
1409
- "SIGN(0x8000000000000000)", -1,
1410
- "SIGN(0xffffffffffffffff)", -1,
1411
- "SIGN(1)", 1,
1412
- "SIGN(1e999)", 1,
1413
- // sentinel
1414
- "NULL", null
1415
- ].forEach(async function (sql, ii, list) {
1416
- let bb = list[ii + 1];
1417
- let cc;
1418
- if (ii % 2 === 1) {
1419
- return;
1420
- }
1421
- ii *= 0.5;
1422
- cc = noop(
1423
- await dbExecAsync({
1424
- db,
1425
- responseType: "dict",
1426
- sql: `SELECT ${sql} AS val`
1427
- })
1428
- )[0][0].val;
1429
- assertJsonEqual(bb, cc, {
1430
- bb,
1431
- cc,
1432
- ii,
1433
- sql
1434
- });
1435
- });
1436
- }
766
+ // This function will recursively deep-copy <obj> with keys sorted.
1437
767
 
1438
- function testDbExecWithRetryAsync() {
1439
- // this function will test dbExecWithRetryAsync's handling-behavior
1440
- // test null-case handling-behavior
1441
- assertErrorThrownAsync(function () {
1442
- return dbExecWithRetryAsync({});
1443
- }, "invalid or closed db");
768
+ let sorted;
769
+ if (typeof obj !== "object" || !obj) {
770
+ return obj;
1444
771
  }
1445
772
 
1446
- async function testDbMemoryXxx() {
1447
- // this function will test dbMemoryXxx's handling-behavior
1448
- let data;
1449
- let db = await dbOpenAsync({
1450
- filename: ":memory:"
1451
- });
1452
- // test null-case handling-behavior
1453
- assertErrorThrownAsync(function () {
1454
- return dbMemoryLoadAsync({
1455
- db
1456
- });
1457
- }, "invalid filename undefined");
1458
- assertErrorThrownAsync(function () {
1459
- return dbMemorySaveAsync({
1460
- db
1461
- });
1462
- }, "invalid filename undefined");
1463
- await dbExecAsync({
1464
- db,
1465
- sql: "CREATE TABLE t01 AS SELECT 1 AS c01"
1466
- });
1467
- await dbMemorySaveAsync({
1468
- db,
1469
- filename: ".testDbMemoryXxx.sqlite"
1470
- });
1471
- db = await dbOpenAsync({
1472
- filename: ":memory:"
1473
- });
1474
- await dbMemoryLoadAsync({
1475
- db,
1476
- filename: ".testDbMemoryXxx.sqlite"
1477
- });
1478
- data = await dbExecAsync({
1479
- db,
1480
- sql: "SELECT * FROM t01"
1481
- });
1482
- assertJsonEqual(data, [
1483
- [
1484
- {
1485
- c01: 1
1486
- }
1487
- ]
1488
- ]);
1489
- }
773
+ // Recursively deep-copy list with child-keys sorted.
1490
774
 
1491
- function testDbOpenAsync() {
1492
- // this function will test dbOpenAsync's handling-behavior
1493
- // test null-case handling-behavior
1494
- assertErrorThrownAsync(function () {
1495
- return dbOpenAsync({});
1496
- }, "invalid filename undefined");
775
+ if (Array.isArray(obj)) {
776
+ return obj.map(objectDeepCopyWithKeysSorted);
1497
777
  }
1498
778
 
1499
- async function testDbTableInsertAsync() {
1500
- // this function will test dbTableInsertAsync's handling-behavior
1501
- let db = await dbOpenAsync({
1502
- filename: ":memory:"
1503
- });
1504
- // test error handling-behavior
1505
- [
1506
- [
1507
- undefined,
1508
- (
1509
- /invalid rowList undefined/
1510
- )
1511
- ], [
1512
- [
1513
- []
1514
- ],
1515
- (
1516
- /invalid colList \[\]/
1517
- )
1518
- ], [
1519
- [
1520
- {}
1521
- ],
1522
- (
1523
- /invalid colList \[\]/
1524
- )
1525
- ]
1526
- ].forEach(function ([
1527
- rowList, rgx
1528
- ]) {
1529
- assertErrorThrownAsync(
1530
- dbTableInsertAsync.bind(
1531
- undefined,
1532
- {
1533
- rowList
1534
- }
1535
- ),
1536
- rgx
1537
- );
1538
- });
1539
- // test csv handling-behavior
1540
- [
1541
- [
1542
- "0", undefined
1543
- ],
1544
- [
1545
- "0,0,0\n1,1,1",
1546
- [
1547
- [
1548
- "c_0", "c_0_2", "c_0_3"
1549
- ], [
1550
- "1", "1", "1"
1551
- ]
1552
- ]
1553
- ],
1554
- [
1555
- (
1556
- "c1,c1,c2\n"
1557
- + "1, 2 \n"
1558
- + `"1","""2""","3\r\n"\n`
1559
- + "\n"
1560
- + "1,2,3\n"
1561
- ),
1562
- [
1563
- [
1564
- "c1", "c1_2", "c2"
1565
- ], [
1566
- "1", " 2 ", null
1567
- ], [
1568
- "1", "\"2\"", "3\n"
1569
- ], [
1570
- "", null, null
1571
- ], [
1572
- "1", "2", "3"
1573
- ]
1574
- ]
1575
- ]
1576
- ].forEach(async function ([
1577
- aa, bb
1578
- ], ii) {
1579
- let cc = noop(
1580
- await dbExecAsync({
1581
- db,
1582
- responseType: "list",
1583
- sql: `SELECT * FROM temp.csv_${ii}`,
1584
- tmpCsv: aa,
1585
- tmpTableName: "csv_" + ii
1586
- })
1587
- )[0];
1588
- assertOrThrow(
1589
- JSON.stringify(bb) === JSON.stringify(cc),
1590
- JSON.stringify([
1591
- ii, aa, bb, cc
1592
- ], undefined, 4)
1593
- );
1594
- });
1595
- }
779
+ // Recursively deep-copy obj with keys sorted.
1596
780
 
1597
- async function testSqlError() {
1598
- // this function will test sql-error's handling-behavior
1599
- let db = await dbOpenAsync({
1600
- filename: ":memory:"
1601
- });
1602
- [
1603
- 1, "SQL logic error",
1604
- 2, "unknown error",
1605
- 3, "access permission denied",
1606
- 4, "query aborted",
1607
- 5, "database is locked",
1608
- 6, "database table is locked",
1609
- 7, "out of memory",
1610
- 8, "attempt to write a readonly database",
1611
- 9, "interrupted",
1612
- 10, "disk I/O error",
1613
- 11, "database disk image is malformed",
1614
- 12, "unknown operation",
1615
- 13, "database or disk is full",
1616
- 14, "unable to open database file",
1617
- 15, "locking protocol",
1618
- 16, "unknown error",
1619
- 17, "database schema has changed",
1620
- 18, "string or blob too big",
1621
- 19, "constraint failed",
1622
- 20, "datatype mismatch",
1623
- 21, "bad parameter or other API misuse",
1624
- 22, "unknown error",
1625
- 23, "authorization denied",
1626
- 24, "unknown error",
1627
- 25, "column index out of range",
1628
- 26, "file is not a database",
1629
- 27, "notification message",
1630
- 28, "warning message",
1631
- 100, "unknown error",
1632
- 101, "unknown error"
1633
- ].forEach(function (sql, ii, list) {
1634
- let bb = list[ii + 1];
1635
- if (ii % 2 === 1) {
1636
- return;
1637
- }
1638
- ii *= 0.5;
1639
- sql = `SELECT throwerror(${sql})`;
1640
- assertErrorThrownAsync(function () {
1641
- return dbExecAsync({
1642
- db,
1643
- sql
1644
- });
1645
- }, bb);
1646
- });
1647
- }
781
+ sorted = Object.create(null);
782
+ Object.keys(obj).sort().forEach(function (key) {
783
+ sorted[key] = objectDeepCopyWithKeysSorted(obj[key]);
784
+ });
785
+ return sorted;
786
+ }
1648
787
 
1649
- async function testSqlExt() {
1650
- // this function will test sql-ext's handling-behavior
1651
- let db = await dbOpenAsync({
1652
- filename: ":memory:"
1653
- });
788
+ async function sqlMessagePost(baton, cFuncName, ...argList) {
789
+
790
+ // This function will post msg to <sqlWorker> and return result
791
+
792
+ let errStack;
793
+ let id;
794
+ let result;
795
+ let timeElapsed = Date.now();
796
+ // increment sqlMessageId
797
+ sqlMessageId += 1;
798
+ id = sqlMessageId;
799
+ // postMessage to web-worker
800
+ sqlWorker.postMessage(
801
+ {
802
+ argList,
803
+ baton,
804
+ cFuncName,
805
+ id
806
+ },
807
+ // transfer arraybuffer without copying
1654
808
  [
1655
- {
1656
- aa: [
1657
- [
1658
- {
1659
- "c01": ""
1660
- }
1661
- ]
1662
- ],
1663
- sql: "SELECT tobase64(NULL) AS c01"
1664
- },
1665
- {
1666
- aa: [
1667
- [
1668
- {
1669
- "c01": ""
1670
- }
1671
- ]
1672
- ],
1673
- sql: "SELECT tobase64(?) AS c01"
1674
- },
1675
- {
1676
- aa: [
1677
- [
1678
- {
1679
- "c01": "AAAAAAAAAAA="
1680
- }
1681
- ]
1682
- ],
1683
- bindList: [
1684
- new Uint8Array(8)
1685
- ],
1686
- sql: "SELECT tobase64(uncompress(compress(?))) AS c01"
1687
- }
1688
- ].forEach(async function ({
1689
- aa,
1690
- bindList,
1691
- sql
1692
- }) {
1693
- let bb = await dbExecAsync({
1694
- bindList,
1695
- db,
1696
- sql
1697
- });
1698
- assertJsonEqual(aa, bb);
1699
- });
809
+ baton.buffer,
810
+ ...argList.filter(function (elem) {
811
+ return elem && elem.constructor === ArrayBuffer;
812
+ })
813
+ ]
814
+ );
815
+ // preserve stack-trace
816
+ errStack = new Error().stack.replace((
817
+ /.*$/m
818
+ ), "");
819
+ // await result from web-worker
820
+ result = await new Promise(function (resolve) {
821
+ sqlMessageDict[id] = resolve;
822
+ });
823
+ // cleanup sqlMessageDict
824
+ delete sqlMessageDict[id];
825
+ // debug slow postMessage
826
+ timeElapsed = Date.now() - timeElapsed;
827
+ if (timeElapsed > 500) {
828
+ consoleError(
829
+ "sqlMessagePost - " + JSON.stringify({
830
+ cFuncName,
831
+ timeElapsed
832
+ }) + errStack
833
+ );
1700
834
  }
835
+ assertOrThrow(!result.errmsg, result.errmsg);
836
+ return [
837
+ result.baton, result.cFuncName, ...result.argList
838
+ ];
839
+ }
1701
840
 
1702
- async function testSqlKthpercentile() {
1703
- // this function will test sql-kthpercentile's handling-behavior
1704
- let db = await dbOpenAsync({
1705
- filename: ":memory:"
1706
- });
1707
- [
1708
- [
1709
- [], -99, 1
1710
- ], [
1711
- [], 0, 1
1712
- ], [
1713
- [], 0.125, 1
1714
- ], [
1715
- [], 0.25, 2
1716
- ], [
1717
- [], 0.375, 3
1718
- ], [
1719
- [], 0.5, 4
1720
- ], [
1721
- [], 0.625, 5
1722
- ], [
1723
- [], 0.75, 6
1724
- ], [
1725
- [], 0.875, 7
1726
- ], [
1727
- [], 1, 8
1728
- ], [
1729
- [], 99, 8
1730
- ], [
1731
- [
1732
- 0.5
1733
- ], 0, 0.5
1734
- ], [
1735
- [
1736
- 0.5
1737
- ], 0.125, 0.5
1738
- ], [
1739
- [
1740
- 1.5
1741
- ], 0.25, 1.5
1742
- ], [
1743
- [
1744
- 2.5
1745
- ], 0.375, 2.5
1746
- ], [
1747
- [
1748
- 3.5
1749
- ], 0.5, 3.5
1750
- ], [
1751
- [
1752
- 4.5
1753
- ], 0.625, 4.5
1754
- ], [
1755
- [
1756
- 5.5
1757
- ], 0.75, 5.5
1758
- ], [
1759
- [
1760
- 6.5
1761
- ], 0.875, 6.5
1762
- ], [
1763
- [
1764
- 7.5
1765
- ], 1, 8
1766
- ]
1767
- ].forEach(async function ([
1768
- data, kk, expected
1769
- ], ii) {
1770
- let actual;
1771
- data = data.concat([
1772
- undefined, undefined, 8, 7, 6, 5, 4, 3, 2, 1, undefined
1773
- ]);
1774
- actual = noop(
1775
- await dbExecAsync({
1776
- db,
1777
- sql: (`
1778
- SELECT kthpercentile(val, ${kk}) AS val FROM __tmp${ii};
1779
- `),
1780
- tmpRowList: data.map(function (val) {
1781
- return {
1782
- val
1783
- };
1784
- }),
1785
- tmpTableName: `__tmp${ii}`
1786
- })
1787
- )[0][0].val;
1788
- assertJsonEqual(actual, expected, {
1789
- data,
1790
- kk
1791
- });
1792
- });
841
+ async function sqlmathInit() {
842
+ dbFinalizationRegistry = new FinalizationRegistry(function ({
843
+ afterFinalization,
844
+ ptr
845
+ }) {
846
+ // This function will auto-close any open sqlite3-db-pointer,
847
+ // after its js-wrapper has been garbage-collected
848
+ cCallAsync(undefined, "_dbClose", ptr[0]);
849
+ if (afterFinalization) {
850
+ afterFinalization();
851
+ }
852
+ });
853
+
854
+ // Feature-detect nodejs.
855
+
856
+ if (
857
+ typeof process === "object"
858
+ && typeof process?.versions?.node === "string"
859
+ ) {
860
+ cModule = await import("module");
861
+ cModule = cModule.createRequire(import.meta.url);
862
+ cModule = cModule(
863
+ "./_binary_sqlmath"
864
+ + "_napi8"
865
+ + "_" + process.platform
866
+ + "_" + process.arch
867
+ + ".node"
868
+ );
869
+ if (process.env.npm_config_mode_test) {
870
+ // mock consoleError
871
+ consoleError = noop;
872
+ }
1793
873
  }
874
+ }
1794
875
 
1795
- addon = requireCjs(
1796
- "./_binary_sqlmath"
1797
- + "_napi8"
1798
- + "_" + process.platform
1799
- + "_" + process.arch
1800
- + ".node"
1801
- );
1802
- testList = [
1803
- testAssertXxx,
1804
- testCcall,
1805
- testDbBind,
1806
- testDbCloseAsync,
1807
- testDbExecAsync,
1808
- testDbExecWithRetryAsync,
1809
- testDbMemoryXxx,
1810
- testDbOpenAsync,
1811
- testDbTableInsertAsync,
1812
- testSqlError,
1813
- testSqlExt,
1814
- testSqlKthpercentile
1815
- ];
1816
- Object.assign(local, {
1817
- SQLITE_MAX_LENGTH2,
1818
- SQLITE_OPEN_AUTOPROXY,
1819
- SQLITE_OPEN_CREATE,
1820
- SQLITE_OPEN_DELETEONCLOSE,
1821
- SQLITE_OPEN_EXCLUSIVE,
1822
- SQLITE_OPEN_FULLMUTEX,
1823
- SQLITE_OPEN_MAIN_DB,
1824
- SQLITE_OPEN_MAIN_JOURNAL,
1825
- SQLITE_OPEN_MEMORY,
1826
- SQLITE_OPEN_NOFOLLOW,
1827
- SQLITE_OPEN_NOMUTEX,
1828
- SQLITE_OPEN_PRIVATECACHE,
1829
- SQLITE_OPEN_READONLY,
1830
- SQLITE_OPEN_READWRITE,
1831
- SQLITE_OPEN_SHAREDCACHE,
1832
- SQLITE_OPEN_SUBJOURNAL,
1833
- SQLITE_OPEN_SUPER_JOURNAL,
1834
- SQLITE_OPEN_TEMP_DB,
1835
- SQLITE_OPEN_TEMP_JOURNAL,
1836
- SQLITE_OPEN_TRANSIENT_DB,
1837
- SQLITE_OPEN_URI,
1838
- SQLITE_OPEN_WAL,
1839
- assertErrorThrownAsync,
1840
- assertNumericalEqual,
1841
- dbCloseAsync,
1842
- dbExecAsync,
1843
- dbExecWithRetryAsync,
1844
- dbGetLastBlobAsync,
1845
- dbMemoryLoadAsync,
1846
- dbMemorySaveAsync,
1847
- dbOpenAsync,
1848
- dbTableInsertAsync,
1849
- debugInline,
1850
- objectDeepCopyWithKeysSorted,
1851
- testAll,
1852
- testList
876
+ function sqlmathWebworkerInit({
877
+ Worker
878
+ }) {
879
+
880
+ // Feature-detect browser.
881
+
882
+ IS_BROWSER = true;
883
+ Worker = Worker || globalThis.Worker;
884
+ sqlWorker = new Worker("sqlmath_wasm.js");
885
+ sqlWorker.onmessage = function ({
886
+ data
887
+ }) {
888
+ sqlMessageDict[data.id](data);
889
+ };
890
+ /*
891
+ let db = await dbOpenAsync({ //jslint-quiet
892
+ filename: ":memory:"
1853
893
  });
1854
- if (process.env.npm_config_mode_test) {
1855
- // mock consoleError
1856
- consoleError = noop;
1857
- }
1858
- }());
894
+ debugInline(
895
+ await dbExecAsync({
896
+ db,
897
+ sql: "aSELECT 1234"
898
+ })
899
+ );
900
+ */
901
+ }
1859
902
 
1860
- export default Object.freeze(local);
903
+ await sqlmathInit({});
904
+
905
+ export {
906
+ SQLITE_MAX_LENGTH2,
907
+ SQLITE_OPEN_AUTOPROXY,
908
+ SQLITE_OPEN_CREATE,
909
+ SQLITE_OPEN_DELETEONCLOSE,
910
+ SQLITE_OPEN_EXCLUSIVE,
911
+ SQLITE_OPEN_FULLMUTEX,
912
+ SQLITE_OPEN_MAIN_DB,
913
+ SQLITE_OPEN_MAIN_JOURNAL,
914
+ SQLITE_OPEN_MEMORY,
915
+ SQLITE_OPEN_NOFOLLOW,
916
+ SQLITE_OPEN_NOMUTEX,
917
+ SQLITE_OPEN_PRIVATECACHE,
918
+ SQLITE_OPEN_READONLY,
919
+ SQLITE_OPEN_READWRITE,
920
+ SQLITE_OPEN_SHAREDCACHE,
921
+ SQLITE_OPEN_SUBJOURNAL,
922
+ SQLITE_OPEN_SUPER_JOURNAL,
923
+ SQLITE_OPEN_TEMP_DB,
924
+ SQLITE_OPEN_TEMP_JOURNAL,
925
+ SQLITE_OPEN_TRANSIENT_DB,
926
+ SQLITE_OPEN_URI,
927
+ SQLITE_OPEN_WAL,
928
+ assertJsonEqual,
929
+ assertNumericalEqual,
930
+ assertOrThrow,
931
+ dbCloseAsync,
932
+ dbExecAndReturnLastBlobAsync,
933
+ dbExecAsync,
934
+ dbFileExportAsync,
935
+ dbFileImportAsync,
936
+ dbNoopAsync,
937
+ dbOpenAsync,
938
+ debugInline,
939
+ jsbatonValueString,
940
+ noop,
941
+ objectDeepCopyWithKeysSorted,
942
+ sqlmathInit,
943
+ sqlmathWebworkerInit
944
+ };