@minnowdb/core 0.7.0 → 0.7.1

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.
Files changed (95) hide show
  1. package/dist/block-format/block.js +254 -325
  2. package/dist/block-format/checksum.js +30 -52
  3. package/dist/block-format/codecs.js +112 -125
  4. package/dist/block-format/column.js +187 -205
  5. package/dist/block-format/index.js +5 -1
  6. package/dist/block-format/physical.js +371 -387
  7. package/dist/block-format/types.js +4 -1
  8. package/dist/block-format/unicode.js +30 -39
  9. package/dist/date-value.js +67 -78
  10. package/dist/engine/artifact-cache.js +106 -116
  11. package/dist/engine/batch.js +36 -44
  12. package/dist/engine/buffered-writer.js +156 -158
  13. package/dist/engine/byte-estimates.js +20 -22
  14. package/dist/engine/cache-limits.js +24 -25
  15. package/dist/engine/cancellation.js +5 -3
  16. package/dist/engine/catalog.js +64 -73
  17. package/dist/engine/client.js +715 -780
  18. package/dist/engine/database.js +15941 -19313
  19. package/dist/engine/defaults.js +104 -111
  20. package/dist/engine/errors.js +113 -128
  21. package/dist/engine/fts.js +233 -280
  22. package/dist/engine/group-index.js +287 -321
  23. package/dist/engine/index.js +23 -12
  24. package/dist/engine/join-index.js +177 -186
  25. package/dist/engine/keyed-live.js +168 -173
  26. package/dist/engine/live-api.js +0 -1
  27. package/dist/engine/live-equal.js +32 -37
  28. package/dist/engine/live.js +603 -647
  29. package/dist/engine/memory.js +109 -111
  30. package/dist/engine/optimizer.d.ts +19 -1
  31. package/dist/engine/optimizer.js +2633 -2934
  32. package/dist/engine/point-read.js +122 -182
  33. package/dist/engine/query-api.js +12 -3
  34. package/dist/engine/query-cache.js +61 -69
  35. package/dist/engine/query.d.ts +6 -0
  36. package/dist/engine/query.js +8503 -9980
  37. package/dist/engine/result-wire.js +221 -252
  38. package/dist/engine/schema-wire.js +97 -112
  39. package/dist/engine/schema.js +1099 -1254
  40. package/dist/engine/sort-keys.js +315 -389
  41. package/dist/engine/sql-domains.js +646 -769
  42. package/dist/engine/sql-driver.js +0 -1
  43. package/dist/engine/sql-functions.js +1031 -1125
  44. package/dist/engine/sql-json.js +199 -218
  45. package/dist/engine/sql-semantics.js +572 -639
  46. package/dist/engine/typed-live.js +271 -293
  47. package/dist/engine/vector.js +4484 -5145
  48. package/dist/engine/worker-host.d.ts +8 -63
  49. package/dist/engine/worker-host.js +28 -1130
  50. package/dist/engine/worker-indexeddb.d.ts +1 -0
  51. package/dist/engine/worker-indexeddb.js +3 -0
  52. package/dist/engine/worker-memory.d.ts +1 -0
  53. package/dist/engine/worker-memory.js +3 -0
  54. package/dist/engine/worker-opfs.d.ts +1 -0
  55. package/dist/engine/worker-opfs.js +3 -0
  56. package/dist/engine/worker-server.d.ts +93 -0
  57. package/dist/engine/worker-server.js +999 -0
  58. package/dist/engine/worker-store-indexeddb.d.ts +2 -0
  59. package/dist/engine/worker-store-indexeddb.js +11 -0
  60. package/dist/engine/worker-store-memory.d.ts +2 -0
  61. package/dist/engine/worker-store-memory.js +6 -0
  62. package/dist/engine/worker-store-opfs.d.ts +2 -0
  63. package/dist/engine/worker-store-opfs.js +9 -0
  64. package/dist/engine/worker.js +0 -12
  65. package/dist/engine/write-block-planner.js +93 -100
  66. package/dist/plan/index.js +15 -16
  67. package/dist/plan/model.d.ts +6 -0
  68. package/dist/plan/model.js +20 -27
  69. package/dist/storage/index.js +0 -12
  70. package/dist/storage/indexeddb.js +11776 -13171
  71. package/dist/storage/memory.js +1198 -1241
  72. package/dist/storage/opfs/files.js +238 -295
  73. package/dist/storage/opfs/index.js +9 -3
  74. package/dist/storage/opfs/leader.js +4386 -4856
  75. package/dist/storage/opfs/rpc.js +223 -259
  76. package/dist/storage/opfs/snapshot-ledger.js +219 -260
  77. package/dist/storage/opfs/store.js +884 -1003
  78. package/dist/storage/persistence.js +59 -74
  79. package/dist/storage/snapshot-stream.js +788 -826
  80. package/dist/storage/snapshot.js +36 -37
  81. package/dist/storage/toolkit/extents.js +444 -520
  82. package/dist/storage/toolkit/index.js +28 -29
  83. package/dist/storage/toolkit/record-core.js +5792 -6377
  84. package/dist/storage/toolkit/sync-file.js +34 -42
  85. package/dist/storage/toolkit/wal.js +92 -127
  86. package/dist/storage/toolkit/wire.js +232 -256
  87. package/dist/storage/types.js +2948 -3250
  88. package/dist/testing/block-store-conformance.js +1556 -1629
  89. package/dist/testing/index.js +310 -291
  90. package/dist/testing/opfs-shim.js +280 -312
  91. package/dist/testing/simulator.js +496 -534
  92. package/dist/testing/sqllogictest.js +425 -365
  93. package/dist/transactions/index.js +1464 -1712
  94. package/dist/worker-protocol/index.js +70 -69
  95. package/package.json +20 -2
@@ -1,434 +1,494 @@
1
- export class SqlLogicParseError extends Error {
2
- location;
3
- constructor(message, location) {
4
- super(`${location.file}:${String(location.line)}: ${message}`);
5
- this.location = location;
6
- this.name = "SqlLogicParseError";
7
- }
1
+ class SqlLogicParseError extends Error {
2
+ location;
3
+ constructor(message, location) {
4
+ super(`${location.file}:${String(location.line)}: ${message}`);
5
+ this.location = location;
6
+ this.name = "SqlLogicParseError";
7
+ }
8
8
  }
9
- export class SqlLogicFailure extends Error {
10
- location;
11
- sql;
12
- constructor(message, location, sql, options) {
13
- super(`${location.file}:${String(location.line)}: ${message}${sql === undefined ? "" : `\n${sql}`}`, options);
14
- this.location = location;
15
- this.sql = sql;
16
- this.name = "SqlLogicFailure";
17
- }
9
+ class SqlLogicFailure extends Error {
10
+ location;
11
+ sql;
12
+ constructor(message, location, sql, options) {
13
+ super(`${location.file}:${String(location.line)}: ${message}${sql === void 0 ? "" : `
14
+ ${sql}`}`, options);
15
+ this.location = location;
16
+ this.sql = sql;
17
+ this.name = "SqlLogicFailure";
18
+ }
18
19
  }
19
- /** Parses the original SQLite SQLLogicTest format, rejecting unknown extensions. */
20
- export function parseSqlLogicTest(source, file = "<memory>") {
21
- const groups = splitRecordGroups(source);
22
- return groups.map((group) => parseRecord(group, file));
20
+ function parseSqlLogicTest(source, file = "<memory>") {
21
+ const groups = splitRecordGroups(source);
22
+ return groups.map((group) => parseRecord(group, file));
23
23
  }
24
- /**
25
- * Parses a line source one record at a time. The full upstream corpus contains very large files;
26
- * this form bounds parser memory to the largest individual record instead of the largest file.
27
- */
28
- export async function* parseSqlLogicTestLines(lines, file = "<stream>") {
29
- let current = [];
30
- let lineNumber = 0;
31
- const finish = () => {
32
- if (current.length === 0)
33
- return undefined;
34
- const record = parseRecord({ lines: current }, file);
35
- current = [];
36
- return record;
37
- };
38
- for await (const original of lines) {
39
- lineNumber++;
40
- const text = original.endsWith("\r") ? original.slice(0, -1) : original;
41
- if (text.startsWith("#"))
42
- continue;
43
- if (text.trim().length === 0) {
44
- const record = finish();
45
- if (record !== undefined)
46
- yield record;
47
- continue;
48
- }
49
- current.push({ text, number: lineNumber });
24
+ async function* parseSqlLogicTestLines(lines, file = "<stream>") {
25
+ let current = [];
26
+ let lineNumber = 0;
27
+ const finish = () => {
28
+ if (current.length === 0)
29
+ return void 0;
30
+ const record2 = parseRecord({ lines: current }, file);
31
+ current = [];
32
+ return record2;
33
+ };
34
+ for await (const original of lines) {
35
+ lineNumber++;
36
+ const text = original.endsWith("\r") ? original.slice(0, -1) : original;
37
+ if (text.startsWith("#"))
38
+ continue;
39
+ if (text.trim().length === 0) {
40
+ const record2 = finish();
41
+ if (record2 !== void 0)
42
+ yield record2;
43
+ continue;
50
44
  }
51
- const record = finish();
52
- if (record !== undefined)
53
- yield record;
45
+ current.push({ text, number: lineNumber });
46
+ }
47
+ const record = finish();
48
+ if (record !== void 0)
49
+ yield record;
54
50
  }
55
51
  function splitRecordGroups(source) {
56
- const lines = source.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
57
- const groups = [];
58
- let current = [];
59
- const finish = () => {
60
- if (current.length > 0)
61
- groups.push({ lines: current });
62
- current = [];
63
- };
64
- for (const [index, text] of lines.entries()) {
65
- if (text.startsWith("#"))
66
- continue;
67
- if (text.trim().length === 0) {
68
- finish();
69
- continue;
70
- }
71
- current.push({ text, number: index + 1 });
52
+ const lines = source.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
53
+ const groups = [];
54
+ let current = [];
55
+ const finish = () => {
56
+ if (current.length > 0)
57
+ groups.push({ lines: current });
58
+ current = [];
59
+ };
60
+ for (const [index, text] of lines.entries()) {
61
+ if (text.startsWith("#"))
62
+ continue;
63
+ if (text.trim().length === 0) {
64
+ finish();
65
+ continue;
72
66
  }
73
- finish();
74
- return groups;
67
+ current.push({ text, number: index + 1 });
68
+ }
69
+ finish();
70
+ return groups;
75
71
  }
76
72
  function parseRecord(group, file) {
77
- let index = 0;
78
- const conditions = [];
79
- while (index < group.lines.length) {
80
- const line = requireLine(group, index, file);
81
- const tokens = tokenizeDirective(line.text);
82
- if (tokens[0] !== "skipif" && tokens[0] !== "onlyif")
83
- break;
84
- if (tokens.length !== 2)
85
- fail("conditional must name exactly one engine", file, line.number);
86
- conditions.push({
87
- kind: tokens[0],
88
- engine: tokens[1] ?? "",
89
- location: { file, line: line.number },
90
- });
91
- index++;
73
+ let index = 0;
74
+ const conditions = [];
75
+ while (index < group.lines.length) {
76
+ const line = requireLine(group, index, file);
77
+ const tokens2 = tokenizeDirective(line.text);
78
+ if (tokens2[0] !== "skipif" && tokens2[0] !== "onlyif")
79
+ break;
80
+ if (tokens2.length !== 2)
81
+ fail("conditional must name exactly one engine", file, line.number);
82
+ conditions.push({
83
+ kind: tokens2[0],
84
+ engine: tokens2[1] ?? "",
85
+ location: { file, line: line.number }
86
+ });
87
+ index++;
88
+ }
89
+ const header = requireLine(group, index, file);
90
+ const tokens = tokenizeDirective(header.text);
91
+ const location = { file, line: header.number };
92
+ if (tokens[0] === "statement") {
93
+ if (tokens.length !== 2 || tokens[1] !== "ok" && tokens[1] !== "error") {
94
+ fail("statement must be 'statement ok' or 'statement error'", file, header.number);
92
95
  }
93
- const header = requireLine(group, index, file);
94
- const tokens = tokenizeDirective(header.text);
95
- const location = { file, line: header.number };
96
- if (tokens[0] === "statement") {
97
- if (tokens.length !== 2 || (tokens[1] !== "ok" && tokens[1] !== "error")) {
98
- fail("statement must be 'statement ok' or 'statement error'", file, header.number);
99
- }
100
- const sql = sqlLines(group.lines.slice(index + 1));
101
- if (sql.length === 0)
102
- fail("statement has no SQL", file, header.number);
103
- return {
104
- kind: "statement",
105
- expectation: tokens[1],
106
- sql,
107
- conditions,
108
- location,
109
- };
96
+ const sql = sqlLines(group.lines.slice(index + 1));
97
+ if (sql.length === 0)
98
+ fail("statement has no SQL", file, header.number);
99
+ return {
100
+ kind: "statement",
101
+ expectation: tokens[1],
102
+ sql,
103
+ conditions,
104
+ location
105
+ };
106
+ }
107
+ if (tokens[0] === "query") {
108
+ if (tokens.length < 2 || tokens.length > 4) {
109
+ fail("query must be 'query <types> [sort-mode] [label]'", file, header.number);
110
110
  }
111
- if (tokens[0] === "query") {
112
- if (tokens.length < 2 || tokens.length > 4) {
113
- fail("query must be 'query <types> [sort-mode] [label]'", file, header.number);
114
- }
115
- const typeString = tokens[1] ?? "";
116
- const types = [];
117
- for (const type of typeString)
118
- types.push(type);
119
- if (types.length === 0 || types.some((type) => type !== "I" && type !== "R" && type !== "T")) {
120
- fail("query type string must contain only I, R, and T", file, header.number);
121
- }
122
- const sortMode = tokens[2] ?? "nosort";
123
- if (sortMode !== "nosort" && sortMode !== "rowsort" && sortMode !== "valuesort") {
124
- fail(`unknown query sort mode '${sortMode}'`, file, header.number);
125
- }
126
- const body = group.lines.slice(index + 1);
127
- const separator = body.findIndex((line) => line.text === "----");
128
- const queryLines = separator < 0 ? body : body.slice(0, separator);
129
- const sql = sqlLines(queryLines);
130
- if (sql.length === 0)
131
- fail("query has no SQL", file, header.number);
132
- const expectedLines = separator < 0 ? [] : body.slice(separator + 1).map((line) => line.text);
133
- return {
134
- kind: "query",
135
- types: types,
136
- sortMode,
137
- ...(tokens[3] === undefined ? {} : { label: tokens[3] }),
138
- sql,
139
- expected: parseExpected(expectedLines, file, header.number),
140
- conditions,
141
- location,
142
- };
111
+ const typeString = tokens[1] ?? "";
112
+ const types = [];
113
+ for (const type of typeString)
114
+ types.push(type);
115
+ if (types.length === 0 || types.some((type) => type !== "I" && type !== "R" && type !== "T")) {
116
+ fail("query type string must contain only I, R, and T", file, header.number);
143
117
  }
144
- if (conditions.length > 0) {
145
- fail("conditionals may prefix only a statement or query", file, header.number);
118
+ const sortMode = tokens[2] ?? "nosort";
119
+ if (sortMode !== "nosort" && sortMode !== "rowsort" && sortMode !== "valuesort") {
120
+ fail(`unknown query sort mode '${sortMode}'`, file, header.number);
146
121
  }
147
- if (tokens[0] === "hash-threshold") {
148
- if (tokens.length !== 2 || !isNonNegativeInteger(tokens[1])) {
149
- fail("hash-threshold must be a non-negative whole number", file, header.number);
150
- }
151
- return { kind: "hash-threshold", valueCount: Number(tokens[1]), location };
122
+ const body = group.lines.slice(index + 1);
123
+ const separator = body.findIndex((line) => line.text === "----");
124
+ const queryLines = separator < 0 ? body : body.slice(0, separator);
125
+ const sql = sqlLines(queryLines);
126
+ if (sql.length === 0)
127
+ fail("query has no SQL", file, header.number);
128
+ const expectedLines = separator < 0 ? [] : body.slice(separator + 1).map((line) => line.text);
129
+ return {
130
+ kind: "query",
131
+ types,
132
+ sortMode,
133
+ ...tokens[3] === void 0 ? {} : { label: tokens[3] },
134
+ sql,
135
+ expected: parseExpected(expectedLines, file, header.number),
136
+ conditions,
137
+ location
138
+ };
139
+ }
140
+ if (conditions.length > 0) {
141
+ fail("conditionals may prefix only a statement or query", file, header.number);
142
+ }
143
+ if (tokens[0] === "hash-threshold") {
144
+ if (tokens.length !== 2 || !isNonNegativeInteger(tokens[1])) {
145
+ fail("hash-threshold must be a non-negative whole number", file, header.number);
152
146
  }
153
- if (tokens[0] === "halt") {
154
- if (tokens.length !== 1 || group.lines.length !== 1) {
155
- fail("halt does not accept arguments", file, header.number);
156
- }
157
- return { kind: "halt", location };
147
+ return { kind: "hash-threshold", valueCount: Number(tokens[1]), location };
148
+ }
149
+ if (tokens[0] === "halt") {
150
+ if (tokens.length !== 1 || group.lines.length !== 1) {
151
+ fail("halt does not accept arguments", file, header.number);
158
152
  }
159
- fail(`unknown SQLLogicTest directive '${tokens[0] ?? ""}'`, file, header.number);
153
+ return { kind: "halt", location };
154
+ }
155
+ fail(`unknown SQLLogicTest directive '${tokens[0] ?? ""}'`, file, header.number);
160
156
  }
161
157
  function parseExpected(lines, file, line) {
162
- if (lines.length !== 1)
163
- return { kind: "values", values: lines };
164
- const match = /^(\d+) values hashing to ([0-9a-fA-F]{32})$/.exec(lines[0] ?? "");
165
- if (match === null)
166
- return { kind: "values", values: lines };
167
- const valueCount = Number(match[1]);
168
- if (!Number.isSafeInteger(valueCount))
169
- fail("hashed value count is too large", file, line);
170
- return { kind: "hash", valueCount, hash: (match[2] ?? "").toLowerCase() };
158
+ if (lines.length !== 1)
159
+ return { kind: "values", values: lines };
160
+ const match = /^(\d+) values hashing to ([0-9a-fA-F]{32})$/.exec(lines[0] ?? "");
161
+ if (match === null)
162
+ return { kind: "values", values: lines };
163
+ const valueCount = Number(match[1]);
164
+ if (!Number.isSafeInteger(valueCount))
165
+ fail("hashed value count is too large", file, line);
166
+ return { kind: "hash", valueCount, hash: (match[2] ?? "").toLowerCase() };
171
167
  }
172
168
  function tokenizeDirective(line) {
173
- return line.trim().split(/\s+/u);
169
+ return line.trim().split(/\s+/u);
174
170
  }
175
171
  function sqlLines(lines) {
176
- return lines.map((line) => line.text).join("\n");
172
+ return lines.map((line) => line.text).join("\n");
177
173
  }
178
174
  function requireLine(group, index, file) {
179
- const line = group.lines[index];
180
- if (line === undefined) {
181
- const last = group.lines.at(-1)?.number ?? 1;
182
- fail("conditional is missing its statement or query", file, last);
183
- }
184
- return line;
175
+ const line = group.lines[index];
176
+ if (line === void 0) {
177
+ const last = group.lines.at(-1)?.number ?? 1;
178
+ fail("conditional is missing its statement or query", file, last);
179
+ }
180
+ return line;
185
181
  }
186
182
  function isNonNegativeInteger(value) {
187
- return value !== undefined && /^\d+$/u.test(value) && Number.isSafeInteger(Number(value));
183
+ return value !== void 0 && /^\d+$/u.test(value) && Number.isSafeInteger(Number(value));
188
184
  }
189
185
  function fail(message, file, line) {
190
- throw new SqlLogicParseError(message, { file, line });
186
+ throw new SqlLogicParseError(message, { file, line });
191
187
  }
192
- /** Runs already-parsed records against one empty database connection. */
193
- export async function runSqlLogicTest(records, database, options = {}) {
194
- let statements = 0;
195
- let queries = 0;
196
- let values = 0;
197
- let skipped = 0;
198
- let hashThreshold = 8;
199
- let halted = false;
200
- const labels = new Map();
201
- try {
202
- for await (const record of records) {
203
- if (record.kind === "hash-threshold") {
204
- hashThreshold = record.valueCount;
205
- continue;
206
- }
207
- if (record.kind === "halt") {
208
- halted = true;
209
- break;
210
- }
211
- if (shouldSkip(record.conditions, database.engineName)) {
212
- skipped++;
213
- if (record.kind === "query" && record.label !== undefined) {
214
- rememberExpectedLabel(record, labels);
215
- }
216
- continue;
217
- }
218
- try {
219
- if (record.kind === "statement") {
220
- statements++;
221
- await runStatement(record, database);
222
- continue;
223
- }
224
- queries++;
225
- const actual = await runQuery(record, database);
226
- values += actual.length;
227
- compareExpected(record, actual, hashThreshold);
228
- rememberActualLabel(record, actual, labels);
229
- }
230
- catch (error) {
231
- if (error instanceof SqlLogicFailure && options.onFailure?.(error, record) === "continue") {
232
- continue;
233
- }
234
- throw error;
235
- }
188
+ async function runSqlLogicTest(records, database, options = {}) {
189
+ let statements = 0;
190
+ let queries = 0;
191
+ let values = 0;
192
+ let skipped = 0;
193
+ let hashThreshold = 8;
194
+ let halted = false;
195
+ const labels = /* @__PURE__ */ new Map();
196
+ try {
197
+ for await (const record of records) {
198
+ if (record.kind === "hash-threshold") {
199
+ hashThreshold = record.valueCount;
200
+ continue;
201
+ }
202
+ if (record.kind === "halt") {
203
+ halted = true;
204
+ break;
205
+ }
206
+ if (shouldSkip(record.conditions, database.engineName)) {
207
+ skipped++;
208
+ if (record.kind === "query" && record.label !== void 0) {
209
+ rememberExpectedLabel(record, labels);
236
210
  }
211
+ continue;
212
+ }
213
+ try {
214
+ if (record.kind === "statement") {
215
+ statements++;
216
+ await runStatement(record, database);
217
+ continue;
218
+ }
219
+ queries++;
220
+ const actual = await runQuery(record, database);
221
+ values += actual.length;
222
+ compareExpected(record, actual, hashThreshold);
223
+ rememberActualLabel(record, actual, labels);
224
+ } catch (error) {
225
+ if (error instanceof SqlLogicFailure && options.onFailure?.(error, record) === "continue") {
226
+ continue;
227
+ }
228
+ throw error;
229
+ }
237
230
  }
238
- finally {
239
- await database.close();
240
- }
241
- return { files: 1, statements, queries, values, skipped, halted, hashThreshold };
231
+ } finally {
232
+ await database.close();
233
+ }
234
+ return { files: 1, statements, queries, values, skipped, halted, hashThreshold };
242
235
  }
243
236
  function shouldSkip(conditions, engineName) {
244
- const engine = engineName.toLowerCase();
245
- return conditions.some((condition) => {
246
- const matches = condition.engine.toLowerCase() === engine;
247
- return condition.kind === "skipif" ? matches : !matches;
248
- });
237
+ const engine = engineName.toLowerCase();
238
+ return conditions.some((condition) => {
239
+ const matches = condition.engine.toLowerCase() === engine;
240
+ return condition.kind === "skipif" ? matches : !matches;
241
+ });
249
242
  }
250
243
  async function runStatement(record, database) {
251
- let failure;
252
- try {
253
- await database.execute(record.sql);
254
- }
255
- catch (error) {
256
- failure = error;
257
- }
258
- if (record.expectation === "error") {
259
- if (failure !== undefined)
260
- return;
261
- throw new SqlLogicFailure("statement succeeded; expected an error", record.location, record.sql);
262
- }
263
- if (failure !== undefined) {
264
- throw new SqlLogicFailure("statement failed; expected success", record.location, record.sql, {
265
- cause: failure,
266
- });
267
- }
244
+ let failure;
245
+ try {
246
+ await database.execute(record.sql);
247
+ } catch (error) {
248
+ failure = error;
249
+ }
250
+ if (record.expectation === "error") {
251
+ if (failure !== void 0)
252
+ return;
253
+ throw new SqlLogicFailure("statement succeeded; expected an error", record.location, record.sql);
254
+ }
255
+ if (failure !== void 0) {
256
+ throw new SqlLogicFailure("statement failed; expected success", record.location, record.sql, {
257
+ cause: failure
258
+ });
259
+ }
268
260
  }
269
261
  async function runQuery(record, database) {
270
- let result;
271
- try {
272
- result = await database.query(record.sql);
273
- }
274
- catch (cause) {
275
- throw new SqlLogicFailure("query failed", record.location, record.sql, { cause });
276
- }
277
- if (result.columns.length !== record.types.length) {
278
- throw new SqlLogicFailure(`wrong column count: expected ${String(record.types.length)}, received ${String(result.columns.length)}`, record.location, record.sql);
279
- }
280
- const rows = result.rows.map((row) => result.columns.map((column, index) => renderSqlLogicValue(row[column] ?? null, record.types[index])));
281
- if (record.sortMode === "rowsort")
282
- rows.sort(compareRows);
283
- const flattened = rows.flat();
284
- if (record.sortMode === "valuesort")
285
- flattened.sort(compareText);
286
- return flattened;
262
+ let result;
263
+ try {
264
+ result = await database.query(record.sql);
265
+ } catch (cause) {
266
+ throw new SqlLogicFailure("query failed", record.location, record.sql, { cause });
267
+ }
268
+ if (result.columns.length !== record.types.length) {
269
+ throw new SqlLogicFailure(`wrong column count: expected ${String(record.types.length)}, received ${String(result.columns.length)}`, record.location, record.sql);
270
+ }
271
+ const rows = result.rows.map((row) => result.columns.map((column, index) => renderSqlLogicValue(row[column] ?? null, record.types[index])));
272
+ if (record.sortMode === "rowsort")
273
+ rows.sort(compareRows);
274
+ const flattened = rows.flat();
275
+ if (record.sortMode === "valuesort")
276
+ flattened.sort(compareText);
277
+ return flattened;
287
278
  }
288
279
  function compareRows(left, right) {
289
- for (let index = 0; index < left.length; index++) {
290
- const comparison = compareText(left[index] ?? "", right[index] ?? "");
291
- if (comparison !== 0)
292
- return comparison;
293
- }
294
- return 0;
280
+ for (let index = 0; index < left.length; index++) {
281
+ const comparison = compareText(left[index] ?? "", right[index] ?? "");
282
+ if (comparison !== 0)
283
+ return comparison;
284
+ }
285
+ return 0;
295
286
  }
296
287
  function compareText(left, right) {
297
- return left < right ? -1 : left > right ? 1 : 0;
288
+ return left < right ? -1 : left > right ? 1 : 0;
298
289
  }
299
- /** Renders one value with the original SQLite SQLLogicTest adapter rules. */
300
- export function renderSqlLogicValue(value, type) {
301
- if (value === null)
302
- return "NULL";
303
- if (type === "I") {
304
- const numeric = typeof value === "boolean" ? Number(value) : Number(value);
305
- if (!Number.isFinite(numeric))
306
- return "0";
307
- return String(Math.trunc(numeric));
308
- }
309
- if (type === "R") {
310
- const numeric = typeof value === "boolean" ? Number(value) : Number(value);
311
- return Number.isFinite(numeric) ? numeric.toFixed(3) : "0.000";
312
- }
313
- const rendered = value instanceof Date ? value.toISOString() : String(value);
314
- if (rendered.length === 0)
315
- return "(empty)";
316
- return rendered.replaceAll(/[^ -~]/gu, "@");
290
+ function renderSqlLogicValue(value, type) {
291
+ if (value === null)
292
+ return "NULL";
293
+ if (type === "I") {
294
+ const numeric = typeof value === "boolean" ? Number(value) : Number(value);
295
+ if (!Number.isFinite(numeric))
296
+ return "0";
297
+ return String(Math.trunc(numeric));
298
+ }
299
+ if (type === "R") {
300
+ const numeric = typeof value === "boolean" ? Number(value) : Number(value);
301
+ return Number.isFinite(numeric) ? numeric.toFixed(3) : "0.000";
302
+ }
303
+ const rendered = value instanceof Date ? value.toISOString() : String(value);
304
+ if (rendered.length === 0)
305
+ return "(empty)";
306
+ return rendered.replaceAll(/[^ -~]/gu, "@");
317
307
  }
318
308
  function compareExpected(record, actual, hashThreshold) {
319
- const hashed = hashThreshold > 0 && actual.length > hashThreshold;
320
- if (hashed) {
321
- if (record.expected.kind !== "hash") {
322
- throw new SqlLogicFailure(`result exceeds hash-threshold ${String(hashThreshold)} but the expected result is not hashed`, record.location, record.sql);
323
- }
324
- const hash = md5Hex(withTrailingNewlines(actual));
325
- if (record.expected.valueCount !== actual.length || record.expected.hash !== hash) {
326
- throw new SqlLogicFailure(`wrong result hash: expected ${String(record.expected.valueCount)} values hashing to ${record.expected.hash}, received ${String(actual.length)} values hashing to ${hash}`, record.location, record.sql);
327
- }
328
- return;
329
- }
330
- if (record.expected.kind === "hash") {
331
- throw new SqlLogicFailure(`expected result is hashed but ${String(actual.length)} values do not exceed hash-threshold ${String(hashThreshold)}`, record.location, record.sql);
309
+ const hashed = hashThreshold > 0 && actual.length > hashThreshold;
310
+ if (hashed) {
311
+ if (record.expected.kind !== "hash") {
312
+ throw new SqlLogicFailure(`result exceeds hash-threshold ${String(hashThreshold)} but the expected result is not hashed`, record.location, record.sql);
332
313
  }
333
- if (!equalValues(record.expected.values, actual)) {
334
- const expected = describeValues(record.expected.values, hashThreshold);
335
- const received = describeValues(actual, hashThreshold);
336
- throw new SqlLogicFailure(`wrong result:\nexpected ${expected}\nreceived ${received}`, record.location, record.sql);
314
+ const hash = md5Hex(withTrailingNewlines(actual));
315
+ if (record.expected.valueCount !== actual.length || record.expected.hash !== hash) {
316
+ throw new SqlLogicFailure(`wrong result hash: expected ${String(record.expected.valueCount)} values hashing to ${record.expected.hash}, received ${String(actual.length)} values hashing to ${hash}`, record.location, record.sql);
337
317
  }
318
+ return;
319
+ }
320
+ if (record.expected.kind === "hash") {
321
+ throw new SqlLogicFailure(`expected result is hashed but ${String(actual.length)} values do not exceed hash-threshold ${String(hashThreshold)}`, record.location, record.sql);
322
+ }
323
+ if (!equalValues(record.expected.values, actual)) {
324
+ const expected = describeValues(record.expected.values, hashThreshold);
325
+ const received = describeValues(actual, hashThreshold);
326
+ throw new SqlLogicFailure(`wrong result:
327
+ expected ${expected}
328
+ received ${received}`, record.location, record.sql);
329
+ }
338
330
  }
339
331
  function equalValues(expected, actual) {
340
- return (expected.length === actual.length && expected.every((value, index) => value === actual[index]));
332
+ return expected.length === actual.length && expected.every((value, index) => value === actual[index]);
341
333
  }
342
334
  function describeValues(values, hashThreshold) {
343
- if (hashThreshold > 0 && values.length > hashThreshold) {
344
- return `${String(values.length)} values hashing to ${md5Hex(withTrailingNewlines(values))}`;
345
- }
346
- return JSON.stringify(values);
335
+ if (hashThreshold > 0 && values.length > hashThreshold) {
336
+ return `${String(values.length)} values hashing to ${md5Hex(withTrailingNewlines(values))}`;
337
+ }
338
+ return JSON.stringify(values);
347
339
  }
348
340
  function rememberExpectedLabel(record, labels) {
349
- if (record.label === undefined)
350
- return;
351
- const hash = record.expected.kind === "hash"
352
- ? record.expected.hash
353
- : md5Hex(withTrailingNewlines(record.expected.values));
354
- rememberLabel(record.label, hash, record.location, labels);
341
+ if (record.label === void 0)
342
+ return;
343
+ const hash = record.expected.kind === "hash" ? record.expected.hash : md5Hex(withTrailingNewlines(record.expected.values));
344
+ rememberLabel(record.label, hash, record.location, labels);
355
345
  }
356
346
  function rememberActualLabel(record, values, labels) {
357
- if (record.label === undefined)
358
- return;
359
- rememberLabel(record.label, md5Hex(withTrailingNewlines(values)), record.location, labels);
347
+ if (record.label === void 0)
348
+ return;
349
+ rememberLabel(record.label, md5Hex(withTrailingNewlines(values)), record.location, labels);
360
350
  }
361
351
  function rememberLabel(label, hash, location, labels) {
362
- const previous = labels.get(label);
363
- if (previous !== undefined && previous.hash !== hash) {
364
- throw new SqlLogicFailure(`labeled result '${label}' differs from ${previous.location.file}:${String(previous.location.line)}`, location);
365
- }
366
- labels.set(label, { hash, location });
352
+ const previous = labels.get(label);
353
+ if (previous !== void 0 && previous.hash !== hash) {
354
+ throw new SqlLogicFailure(`labeled result '${label}' differs from ${previous.location.file}:${String(previous.location.line)}`, location);
355
+ }
356
+ labels.set(label, { hash, location });
367
357
  }
368
358
  function withTrailingNewlines(values) {
369
- return values.map((value) => `${value}\n`).join("");
359
+ return values.map((value) => `${value}
360
+ `).join("");
370
361
  }
371
- /** Small browser-safe MD5 implementation for compatibility with the original corpus hashes. */
372
- export function md5Hex(input) {
373
- const bytes = new TextEncoder().encode(input);
374
- const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;
375
- const padded = new Uint8Array(paddedLength);
376
- padded.set(bytes);
377
- padded[bytes.length] = 0x80;
378
- const bitLength = BigInt(bytes.length) * 8n;
379
- const view = new DataView(padded.buffer);
380
- view.setUint32(paddedLength - 8, Number(bitLength & 0xffffffffn), true);
381
- view.setUint32(paddedLength - 4, Number((bitLength >> 32n) & 0xffffffffn), true);
382
- let a0 = 0x67452301;
383
- let b0 = 0xefcdab89;
384
- let c0 = 0x98badcfe;
385
- let d0 = 0x10325476;
386
- const words = new Uint32Array(16);
387
- for (let offset = 0; offset < paddedLength; offset += 64) {
388
- for (let index = 0; index < 16; index++)
389
- words[index] = view.getUint32(offset + index * 4, true);
390
- let a = a0;
391
- let b = b0;
392
- let c = c0;
393
- let d = d0;
394
- for (let index = 0; index < 64; index++) {
395
- const { mix, word } = md5Round(index, b, c, d);
396
- const rotated = rotateLeft((a + mix + (MD5_CONSTANTS[index] ?? 0) + (words[word] ?? 0)) >>> 0, MD5_SHIFTS[index]);
397
- a = d;
398
- d = c;
399
- c = b;
400
- b = (b + rotated) >>> 0;
401
- }
402
- a0 = (a0 + a) >>> 0;
403
- b0 = (b0 + b) >>> 0;
404
- c0 = (c0 + c) >>> 0;
405
- d0 = (d0 + d) >>> 0;
362
+ function md5Hex(input) {
363
+ const bytes = new TextEncoder().encode(input);
364
+ const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;
365
+ const padded = new Uint8Array(paddedLength);
366
+ padded.set(bytes);
367
+ padded[bytes.length] = 128;
368
+ const bitLength = BigInt(bytes.length) * 8n;
369
+ const view = new DataView(padded.buffer);
370
+ view.setUint32(paddedLength - 8, Number(bitLength & 0xffffffffn), true);
371
+ view.setUint32(paddedLength - 4, Number(bitLength >> 32n & 0xffffffffn), true);
372
+ let a0 = 1732584193;
373
+ let b0 = 4023233417;
374
+ let c0 = 2562383102;
375
+ let d0 = 271733878;
376
+ const words = new Uint32Array(16);
377
+ for (let offset = 0; offset < paddedLength; offset += 64) {
378
+ for (let index = 0; index < 16; index++)
379
+ words[index] = view.getUint32(offset + index * 4, true);
380
+ let a = a0;
381
+ let b = b0;
382
+ let c = c0;
383
+ let d = d0;
384
+ for (let index = 0; index < 64; index++) {
385
+ const { mix, word } = md5Round(index, b, c, d);
386
+ const rotated = rotateLeft(a + mix + (MD5_CONSTANTS[index] ?? 0) + (words[word] ?? 0) >>> 0, MD5_SHIFTS[index]);
387
+ a = d;
388
+ d = c;
389
+ c = b;
390
+ b = b + rotated >>> 0;
406
391
  }
407
- return [a0, b0, c0, d0].map(littleEndianHex).join("");
392
+ a0 = a0 + a >>> 0;
393
+ b0 = b0 + b >>> 0;
394
+ c0 = c0 + c >>> 0;
395
+ d0 = d0 + d >>> 0;
396
+ }
397
+ return [a0, b0, c0, d0].map(littleEndianHex).join("");
408
398
  }
409
399
  function md5Round(index, b, c, d) {
410
- if (index < 16)
411
- return { mix: (b & c) | (~b & d), word: index };
412
- if (index < 32)
413
- return { mix: (d & b) | (~d & c), word: (5 * index + 1) % 16 };
414
- if (index < 48)
415
- return { mix: b ^ c ^ d, word: (3 * index + 5) % 16 };
416
- return { mix: c ^ (b | ~d), word: (7 * index) % 16 };
400
+ if (index < 16)
401
+ return { mix: b & c | ~b & d, word: index };
402
+ if (index < 32)
403
+ return { mix: d & b | ~d & c, word: (5 * index + 1) % 16 };
404
+ if (index < 48)
405
+ return { mix: b ^ c ^ d, word: (3 * index + 5) % 16 };
406
+ return { mix: c ^ (b | ~d), word: 7 * index % 16 };
417
407
  }
418
408
  function rotateLeft(value, shift) {
419
- const bits = shift ?? 0;
420
- return ((value << bits) | (value >>> (32 - bits))) >>> 0;
409
+ const bits = shift ?? 0;
410
+ return (value << bits | value >>> 32 - bits) >>> 0;
421
411
  }
422
412
  function littleEndianHex(value) {
423
- let output = "";
424
- for (let index = 0; index < 4; index++) {
425
- output += ((value >>> (index * 8)) & 0xff).toString(16).padStart(2, "0");
426
- }
427
- return output;
413
+ let output = "";
414
+ for (let index = 0; index < 4; index++) {
415
+ output += (value >>> index * 8 & 255).toString(16).padStart(2, "0");
416
+ }
417
+ return output;
428
418
  }
429
419
  const MD5_SHIFTS = [
430
- 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14,
431
- 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6,
432
- 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
420
+ 7,
421
+ 12,
422
+ 17,
423
+ 22,
424
+ 7,
425
+ 12,
426
+ 17,
427
+ 22,
428
+ 7,
429
+ 12,
430
+ 17,
431
+ 22,
432
+ 7,
433
+ 12,
434
+ 17,
435
+ 22,
436
+ 5,
437
+ 9,
438
+ 14,
439
+ 20,
440
+ 5,
441
+ 9,
442
+ 14,
443
+ 20,
444
+ 5,
445
+ 9,
446
+ 14,
447
+ 20,
448
+ 5,
449
+ 9,
450
+ 14,
451
+ 20,
452
+ 4,
453
+ 11,
454
+ 16,
455
+ 23,
456
+ 4,
457
+ 11,
458
+ 16,
459
+ 23,
460
+ 4,
461
+ 11,
462
+ 16,
463
+ 23,
464
+ 4,
465
+ 11,
466
+ 16,
467
+ 23,
468
+ 6,
469
+ 10,
470
+ 15,
471
+ 21,
472
+ 6,
473
+ 10,
474
+ 15,
475
+ 21,
476
+ 6,
477
+ 10,
478
+ 15,
479
+ 21,
480
+ 6,
481
+ 10,
482
+ 15,
483
+ 21
433
484
  ];
434
485
  const MD5_CONSTANTS = Array.from({ length: 64 }, (_, index) => Math.floor(Math.abs(Math.sin(index + 1)) * 2 ** 32));
486
+ export {
487
+ SqlLogicFailure,
488
+ SqlLogicParseError,
489
+ md5Hex,
490
+ parseSqlLogicTest,
491
+ parseSqlLogicTestLines,
492
+ renderSqlLogicValue,
493
+ runSqlLogicTest
494
+ };