@minnowdb/core 0.3.0 → 0.4.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.
- package/README.md +4 -2
- package/dist/block-format/block.d.ts +32 -13
- package/dist/block-format/block.js +179 -55
- package/dist/block-format/checksum.d.ts +2 -1
- package/dist/block-format/checksum.js +5 -2
- package/dist/block-format/codecs.d.ts +7 -2
- package/dist/block-format/codecs.js +53 -12
- package/dist/block-format/column.d.ts +3 -2
- package/dist/block-format/column.js +96 -30
- package/dist/block-format/index.d.ts +2 -2
- package/dist/block-format/index.js +2 -2
- package/dist/block-format/physical.d.ts +8 -2
- package/dist/block-format/physical.js +14 -6
- package/dist/block-format/types.d.ts +7 -3
- package/dist/block-format/types.js +0 -1
- package/dist/block-format/unicode.d.ts +11 -0
- package/dist/block-format/unicode.js +46 -0
- package/dist/date-value.d.ts +15 -0
- package/dist/date-value.js +64 -0
- package/dist/engine/artifact-cache.d.ts +0 -1
- package/dist/engine/artifact-cache.js +0 -1
- package/dist/engine/batch.d.ts +5 -2
- package/dist/engine/batch.js +0 -1
- package/dist/engine/buffered-writer.d.ts +2 -1
- package/dist/engine/buffered-writer.js +34 -9
- package/dist/engine/cache-limits.d.ts +25 -0
- package/dist/engine/cache-limits.js +25 -0
- package/dist/engine/catalog.d.ts +5 -6
- package/dist/engine/catalog.js +5 -5
- package/dist/engine/client.d.ts +27 -7
- package/dist/engine/client.js +197 -31
- package/dist/engine/database.d.ts +161 -38
- package/dist/engine/database.js +4326 -1523
- package/dist/engine/defaults.d.ts +0 -1
- package/dist/engine/defaults.js +2 -4
- package/dist/engine/errors.d.ts +19 -1
- package/dist/engine/errors.js +32 -2
- package/dist/engine/fts.d.ts +0 -6
- package/dist/engine/fts.js +39 -14
- package/dist/engine/group-index.d.ts +0 -1
- package/dist/engine/group-index.js +6 -11
- package/dist/engine/index.d.ts +16 -11
- package/dist/engine/index.js +12 -10
- package/dist/engine/join-index.d.ts +0 -1
- package/dist/engine/join-index.js +2 -2
- package/dist/engine/keyed-live.d.ts +57 -0
- package/dist/engine/keyed-live.js +226 -0
- package/dist/engine/live-api.d.ts +4 -0
- package/dist/engine/live-api.js +4 -0
- package/dist/engine/live.d.ts +43 -26
- package/dist/engine/live.js +499 -129
- package/dist/engine/memory.d.ts +2 -1
- package/dist/engine/memory.js +3 -2
- package/dist/engine/optimizer.d.ts +0 -1
- package/dist/engine/optimizer.js +4 -4
- package/dist/engine/query-api.d.ts +3 -0
- package/dist/engine/query-api.js +3 -0
- package/dist/engine/query-cache.d.ts +0 -1
- package/dist/engine/query-cache.js +4 -4
- package/dist/engine/query.d.ts +4 -7
- package/dist/engine/query.js +348 -155
- package/dist/engine/result-wire.d.ts +0 -1
- package/dist/engine/result-wire.js +2 -2
- package/dist/engine/schema-wire.d.ts +1 -2
- package/dist/engine/schema-wire.js +0 -1
- package/dist/engine/schema.d.ts +3 -8
- package/dist/engine/schema.js +41 -24
- package/dist/engine/sort-keys.d.ts +0 -1
- package/dist/engine/sort-keys.js +2 -2
- package/dist/engine/sql-domains.d.ts +6 -1
- package/dist/engine/sql-domains.js +209 -45
- package/dist/engine/sql-driver.d.ts +2 -2
- package/dist/engine/sql-driver.js +0 -1
- package/dist/engine/sql-json.d.ts +0 -1
- package/dist/engine/sql-json.js +53 -7
- package/dist/engine/sql-semantics.d.ts +8 -5
- package/dist/engine/sql-semantics.js +427 -65
- package/dist/engine/typed-live.d.ts +67 -0
- package/dist/engine/typed-live.js +349 -0
- package/dist/engine/vector.d.ts +7 -1
- package/dist/engine/vector.js +202 -58
- package/dist/engine/worker-host.d.ts +8 -2
- package/dist/engine/worker-host.js +616 -152
- package/dist/engine/worker.d.ts +0 -1
- package/dist/engine/worker.js +3 -3
- package/dist/engine/write-block-planner.d.ts +18 -0
- package/dist/engine/write-block-planner.js +112 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/plan/index.d.ts +0 -1
- package/dist/plan/index.js +0 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/storage/indexeddb.d.ts +74 -65
- package/dist/storage/indexeddb.js +12301 -2076
- package/dist/storage/memory.d.ts +56 -66
- package/dist/storage/memory.js +1004 -139
- package/dist/storage/opfs/files.d.ts +18 -7
- package/dist/storage/opfs/files.js +115 -28
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +1 -1
- package/dist/storage/opfs/leader.d.ts +403 -91
- package/dist/storage/opfs/leader.js +4318 -602
- package/dist/storage/opfs/rpc.d.ts +15 -3
- package/dist/storage/opfs/rpc.js +223 -2
- package/dist/storage/opfs/snapshot-ledger.d.ts +40 -0
- package/dist/storage/opfs/snapshot-ledger.js +281 -0
- package/dist/storage/opfs/store.d.ts +26 -121
- package/dist/storage/opfs/store.js +436 -388
- package/dist/storage/persistence.d.ts +37 -0
- package/dist/storage/persistence.js +78 -0
- package/dist/storage/snapshot-stream.d.ts +24 -0
- package/dist/storage/snapshot-stream.js +897 -0
- package/dist/storage/snapshot.d.ts +9 -99
- package/dist/storage/snapshot.js +33 -309
- package/dist/storage/toolkit/extents.d.ts +41 -7
- package/dist/storage/toolkit/extents.js +402 -39
- package/dist/storage/toolkit/index.d.ts +3 -4
- package/dist/storage/toolkit/index.js +27 -3
- package/dist/storage/toolkit/record-core.d.ts +174 -61
- package/dist/storage/toolkit/record-core.js +5401 -849
- package/dist/storage/toolkit/sync-file.d.ts +10 -1
- package/dist/storage/toolkit/sync-file.js +50 -2
- package/dist/storage/toolkit/wal.d.ts +23 -5
- package/dist/storage/toolkit/wal.js +89 -27
- package/dist/storage/toolkit/wire.d.ts +4 -6
- package/dist/storage/toolkit/wire.js +80 -25
- package/dist/storage/types.d.ts +1271 -218
- package/dist/storage/types.js +1471 -138
- package/dist/testing/block-store-conformance.d.ts +0 -1
- package/dist/testing/block-store-conformance.js +877 -138
- package/dist/testing/index.d.ts +42 -31
- package/dist/testing/index.js +100 -68
- package/dist/testing/opfs-shim.d.ts +13 -3
- package/dist/testing/opfs-shim.js +40 -6
- package/dist/testing/simulator.d.ts +0 -1
- package/dist/testing/simulator.js +43 -12
- package/dist/testing/sqllogictest.d.ts +0 -1
- package/dist/testing/sqllogictest.js +0 -1
- package/dist/transactions/index.d.ts +81 -20
- package/dist/transactions/index.js +1050 -268
- package/dist/worker-protocol/index.d.ts +3 -2
- package/dist/worker-protocol/index.js +2 -5
- package/package.json +49 -1
- package/dist/block-format/block.d.ts.map +0 -1
- package/dist/block-format/block.js.map +0 -1
- package/dist/block-format/checksum.d.ts.map +0 -1
- package/dist/block-format/checksum.js.map +0 -1
- package/dist/block-format/codecs.d.ts.map +0 -1
- package/dist/block-format/codecs.js.map +0 -1
- package/dist/block-format/column.d.ts.map +0 -1
- package/dist/block-format/column.js.map +0 -1
- package/dist/block-format/index.d.ts.map +0 -1
- package/dist/block-format/index.js.map +0 -1
- package/dist/block-format/physical.d.ts.map +0 -1
- package/dist/block-format/physical.js.map +0 -1
- package/dist/block-format/types.d.ts.map +0 -1
- package/dist/block-format/types.js.map +0 -1
- package/dist/engine/artifact-cache.d.ts.map +0 -1
- package/dist/engine/artifact-cache.js.map +0 -1
- package/dist/engine/batch.d.ts.map +0 -1
- package/dist/engine/batch.js.map +0 -1
- package/dist/engine/buffered-writer.d.ts.map +0 -1
- package/dist/engine/buffered-writer.js.map +0 -1
- package/dist/engine/catalog.d.ts.map +0 -1
- package/dist/engine/catalog.js.map +0 -1
- package/dist/engine/client.d.ts.map +0 -1
- package/dist/engine/client.js.map +0 -1
- package/dist/engine/coordinator.d.ts +0 -17
- package/dist/engine/coordinator.d.ts.map +0 -1
- package/dist/engine/coordinator.js +0 -60
- package/dist/engine/coordinator.js.map +0 -1
- package/dist/engine/database.d.ts.map +0 -1
- package/dist/engine/database.js.map +0 -1
- package/dist/engine/defaults.d.ts.map +0 -1
- package/dist/engine/defaults.js.map +0 -1
- package/dist/engine/errors.d.ts.map +0 -1
- package/dist/engine/errors.js.map +0 -1
- package/dist/engine/fts.d.ts.map +0 -1
- package/dist/engine/fts.js.map +0 -1
- package/dist/engine/group-index.d.ts.map +0 -1
- package/dist/engine/group-index.js.map +0 -1
- package/dist/engine/index.d.ts.map +0 -1
- package/dist/engine/index.js.map +0 -1
- package/dist/engine/join-index.d.ts.map +0 -1
- package/dist/engine/join-index.js.map +0 -1
- package/dist/engine/live.d.ts.map +0 -1
- package/dist/engine/live.js.map +0 -1
- package/dist/engine/memory.d.ts.map +0 -1
- package/dist/engine/memory.js.map +0 -1
- package/dist/engine/optimizer.d.ts.map +0 -1
- package/dist/engine/optimizer.js.map +0 -1
- package/dist/engine/query-cache.d.ts.map +0 -1
- package/dist/engine/query-cache.js.map +0 -1
- package/dist/engine/query.d.ts.map +0 -1
- package/dist/engine/query.js.map +0 -1
- package/dist/engine/result-wire.d.ts.map +0 -1
- package/dist/engine/result-wire.js.map +0 -1
- package/dist/engine/schema-wire.d.ts.map +0 -1
- package/dist/engine/schema-wire.js.map +0 -1
- package/dist/engine/schema.d.ts.map +0 -1
- package/dist/engine/schema.js.map +0 -1
- package/dist/engine/sort-keys.d.ts.map +0 -1
- package/dist/engine/sort-keys.js.map +0 -1
- package/dist/engine/sql-domains.d.ts.map +0 -1
- package/dist/engine/sql-domains.js.map +0 -1
- package/dist/engine/sql-driver.d.ts.map +0 -1
- package/dist/engine/sql-driver.js.map +0 -1
- package/dist/engine/sql-json.d.ts.map +0 -1
- package/dist/engine/sql-json.js.map +0 -1
- package/dist/engine/sql-semantics.d.ts.map +0 -1
- package/dist/engine/sql-semantics.js.map +0 -1
- package/dist/engine/vector.d.ts.map +0 -1
- package/dist/engine/vector.js.map +0 -1
- package/dist/engine/worker-host.d.ts.map +0 -1
- package/dist/engine/worker-host.js.map +0 -1
- package/dist/engine/worker.d.ts.map +0 -1
- package/dist/engine/worker.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/plan/index.d.ts.map +0 -1
- package/dist/plan/index.js.map +0 -1
- package/dist/storage/fixture-shape.d.ts +0 -42
- package/dist/storage/fixture-shape.d.ts.map +0 -1
- package/dist/storage/fixture-shape.js +0 -146
- package/dist/storage/fixture-shape.js.map +0 -1
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/indexeddb.d.ts.map +0 -1
- package/dist/storage/indexeddb.js.map +0 -1
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js.map +0 -1
- package/dist/storage/opfs/files.d.ts.map +0 -1
- package/dist/storage/opfs/files.js.map +0 -1
- package/dist/storage/opfs/index.d.ts.map +0 -1
- package/dist/storage/opfs/index.js.map +0 -1
- package/dist/storage/opfs/leader.d.ts.map +0 -1
- package/dist/storage/opfs/leader.js.map +0 -1
- package/dist/storage/opfs/rpc.d.ts.map +0 -1
- package/dist/storage/opfs/rpc.js.map +0 -1
- package/dist/storage/opfs/store.d.ts.map +0 -1
- package/dist/storage/opfs/store.js.map +0 -1
- package/dist/storage/snapshot.d.ts.map +0 -1
- package/dist/storage/snapshot.js.map +0 -1
- package/dist/storage/toolkit/extents.d.ts.map +0 -1
- package/dist/storage/toolkit/extents.js.map +0 -1
- package/dist/storage/toolkit/index.d.ts.map +0 -1
- package/dist/storage/toolkit/index.js.map +0 -1
- package/dist/storage/toolkit/record-core.d.ts.map +0 -1
- package/dist/storage/toolkit/record-core.js.map +0 -1
- package/dist/storage/toolkit/sync-file.d.ts.map +0 -1
- package/dist/storage/toolkit/sync-file.js.map +0 -1
- package/dist/storage/toolkit/wal.d.ts.map +0 -1
- package/dist/storage/toolkit/wal.js.map +0 -1
- package/dist/storage/toolkit/wire.d.ts.map +0 -1
- package/dist/storage/toolkit/wire.js.map +0 -1
- package/dist/storage/types.d.ts.map +0 -1
- package/dist/storage/types.js.map +0 -1
- package/dist/testing/block-store-conformance.d.ts.map +0 -1
- package/dist/testing/block-store-conformance.js.map +0 -1
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js.map +0 -1
- package/dist/testing/opfs-shim.d.ts.map +0 -1
- package/dist/testing/opfs-shim.js.map +0 -1
- package/dist/testing/seeds.d.ts +0 -11
- package/dist/testing/seeds.d.ts.map +0 -1
- package/dist/testing/seeds.js +0 -50
- package/dist/testing/seeds.js.map +0 -1
- package/dist/testing/simulator.d.ts.map +0 -1
- package/dist/testing/simulator.js.map +0 -1
- package/dist/testing/sqllogictest.d.ts.map +0 -1
- package/dist/testing/sqllogictest.js.map +0 -1
- package/dist/transactions/index.d.ts.map +0 -1
- package/dist/transactions/index.js.map +0 -1
- package/dist/worker-protocol/index.d.ts.map +0 -1
- package/dist/worker-protocol/index.js.map +0 -1
package/dist/engine/query.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { copyDate, dateIsoString, dateMilliseconds, dateUtcDate, dateUtcDay, dateUtcFullYear, dateUtcHours, dateUtcMinutes, dateUtcMonth, dateUtcSeconds, setDateUtcDate, setDateUtcMonth, } from "../date-value.js";
|
|
2
|
+
import { assertWellFormedString, wellFormedUtf8ByteLength } from "../block-format/unicode.js";
|
|
3
|
+
import { MAX_SQL_NESTING_DEPTH, MAX_SQL_PARAMETERS, MAX_SQL_SCALAR_RESULT_CHARACTERS, MAX_SQL_TEXT_CHARACTERS, MAX_SQL_TOKENS, } from "./cache-limits.js";
|
|
1
4
|
import { SqlCompileError } from "./errors.js";
|
|
2
5
|
import { cachedQueryTerms, ftsBm25Row, FtsStatsAccumulator, ftsMatchTruth, renderDocumentValue, tokenize as ftsTokenize, validateFtsQuery, } from "./fts.js";
|
|
3
6
|
import { QueryMemoryContext } from "./memory.js";
|
|
@@ -6,7 +9,7 @@ import { stringArgument } from "./sql-semantics.js";
|
|
|
6
9
|
import { jsonAtPath, jsonConstructor, jsonIsValid, jsonValueOf, parseJsonPath, } from "./sql-json.js";
|
|
7
10
|
import { optimizePlan } from "./optimizer.js";
|
|
8
11
|
import { compareSqlValues as compareValues, compileLikePattern, compileSimilarPattern, encodeSqlEqualityValue, roundSqlNumber, } from "./sql-semantics.js";
|
|
9
|
-
import { arrayDomainValue, collatedDomainValue, exactNumericBinary, exactNumericValue, externalSqlDomainValue, intervalDomainValue, isExactNumeric, isSqlDomainValue, jsonDomainValue, normalizeSqlDomainValue, protectedSqlTextValue, timeDomainValue, uuidDomainValue, } from "./sql-domains.js";
|
|
12
|
+
import { arrayDomainValue, boundedJsonText, collatedDomainValue, exactNumericBinary, exactNumericValue, externalSqlDomainValue, intervalDomainValue, isExactNumeric, isSqlDomainValue, jsonDomainValue, normalizeSqlDomainValue, protectedSqlTextValue, timeDomainValue, uuidDomainValue, } from "./sql-domains.js";
|
|
10
13
|
import { columnarTableFromRows, prepareVectorQuery, } from "./vector.js";
|
|
11
14
|
export const scalarFunctionNames = new Set([
|
|
12
15
|
"ROUND",
|
|
@@ -148,7 +151,7 @@ function castValue(value, target) {
|
|
|
148
151
|
if (typeof value === "boolean")
|
|
149
152
|
return protectedSqlTextValue(value ? "true" : "false");
|
|
150
153
|
if (value instanceof Date)
|
|
151
|
-
return protectedSqlTextValue(value
|
|
154
|
+
return protectedSqlTextValue(dateIsoString(value));
|
|
152
155
|
}
|
|
153
156
|
if (target === "number" || target === "number-integer") {
|
|
154
157
|
let parsed;
|
|
@@ -198,7 +201,7 @@ function castValue(value, target) {
|
|
|
198
201
|
return value;
|
|
199
202
|
if (typeof value === "string" || typeof value === "number") {
|
|
200
203
|
const parsed = new Date(value);
|
|
201
|
-
if (Number.isFinite(parsed
|
|
204
|
+
if (Number.isFinite(dateMilliseconds(parsed)))
|
|
202
205
|
return parsed;
|
|
203
206
|
throw new TypeError(`Cannot cast this value to a datetime: ${String(value)}`);
|
|
204
207
|
}
|
|
@@ -262,7 +265,7 @@ export function scalarFunctionValue(name, values) {
|
|
|
262
265
|
// datetimes distinct, so the ordinary single-key hash join can safely carry a composite.
|
|
263
266
|
if (values.some((value) => value === null || value === undefined))
|
|
264
267
|
return null;
|
|
265
|
-
return
|
|
268
|
+
return boundedJsonText(values.map(encodeSqlEqualityValue), false, "Composite key");
|
|
266
269
|
}
|
|
267
270
|
const first = values[0];
|
|
268
271
|
if (first === null || first === undefined)
|
|
@@ -307,10 +310,16 @@ export function scalarFunctionValue(name, values) {
|
|
|
307
310
|
return null;
|
|
308
311
|
if (values[2] === null || values[2] === undefined)
|
|
309
312
|
return null;
|
|
313
|
+
const source = stringArgument("REPLACE", first);
|
|
310
314
|
const search = stringArgument("REPLACE", values[1]);
|
|
315
|
+
const replacement = stringArgument("REPLACE", values[2]);
|
|
316
|
+
assertScalarInputLength(source, "REPLACE source");
|
|
317
|
+
assertScalarInputLength(search, "REPLACE search text");
|
|
318
|
+
assertScalarInputLength(replacement, "REPLACE replacement text");
|
|
311
319
|
if (search === "")
|
|
312
|
-
return
|
|
313
|
-
|
|
320
|
+
return source;
|
|
321
|
+
assertReplacementResultLength(source, search, replacement);
|
|
322
|
+
return protectedSqlTextValue(source.split(search).join(replacement));
|
|
314
323
|
}
|
|
315
324
|
case "INSTR": {
|
|
316
325
|
if (values[1] === null || values[1] === undefined)
|
|
@@ -319,7 +328,7 @@ export function scalarFunctionValue(name, values) {
|
|
|
319
328
|
const needle = stringArgument("INSTR", values[1]);
|
|
320
329
|
const index = haystack.indexOf(needle);
|
|
321
330
|
// 1-based character position, 0 when absent, counting codepoints like LENGTH.
|
|
322
|
-
return index === -1 ? 0 :
|
|
331
|
+
return index === -1 ? 0 : codePointLength(haystack, index) + 1;
|
|
323
332
|
}
|
|
324
333
|
case "EXTRACT":
|
|
325
334
|
return extractDatePart(typeof first === "string" ? first : "", values[1]);
|
|
@@ -331,17 +340,23 @@ export function scalarFunctionValue(name, values) {
|
|
|
331
340
|
}
|
|
332
341
|
case "ABS":
|
|
333
342
|
return Math.abs(numeric(first));
|
|
334
|
-
case "UPPER":
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
return
|
|
343
|
+
case "UPPER": {
|
|
344
|
+
const source = stringArgument("UPPER", first);
|
|
345
|
+
assertScalarInputLength(source, "UPPER input");
|
|
346
|
+
return boundedScalarResult(source.toUpperCase(), "UPPER result");
|
|
347
|
+
}
|
|
348
|
+
case "LOWER": {
|
|
349
|
+
const source = stringArgument("LOWER", first);
|
|
350
|
+
assertScalarInputLength(source, "LOWER input");
|
|
351
|
+
return boundedScalarResult(source.toLowerCase(), "LOWER result");
|
|
352
|
+
}
|
|
338
353
|
case "TRIM":
|
|
339
354
|
// SQL TRIM removes spaces, not general whitespace.
|
|
340
355
|
return trimEnds("TRIM", first, values[1], "both");
|
|
341
356
|
case "LENGTH":
|
|
342
|
-
return
|
|
357
|
+
return codePointLength(stringArgument("LENGTH", first));
|
|
343
358
|
case "OCTET_LENGTH":
|
|
344
|
-
return
|
|
359
|
+
return wellFormedUtf8ByteLength(stringArgument("OCTET_LENGTH", first), "OCTET_LENGTH input");
|
|
345
360
|
case "IS_JSON": {
|
|
346
361
|
const kind = values[1];
|
|
347
362
|
return jsonIsValid(first, typeof kind === "string" ? kind : "value");
|
|
@@ -375,24 +390,24 @@ export function scalarFunctionValue(name, values) {
|
|
|
375
390
|
if (!Number.isInteger(width) || width < 0) {
|
|
376
391
|
throw new TypeError(`${name} length must be a non-negative integer`);
|
|
377
392
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
393
|
+
if (width > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
394
|
+
throw new RangeError(`${name} result exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
395
|
+
}
|
|
396
|
+
const source = stringArgument(name, first);
|
|
397
|
+
const text = codePointPrefix(source, width);
|
|
398
|
+
if (text.count >= width)
|
|
399
|
+
return boundedScalarResult(text.text, `${name} result`);
|
|
381
400
|
let fill = " ";
|
|
382
401
|
if (values.length > 2) {
|
|
383
402
|
if (values[2] === null || values[2] === undefined)
|
|
384
403
|
return null;
|
|
385
404
|
fill = stringArgument(name, values[2]);
|
|
386
405
|
}
|
|
387
|
-
const filler = Array.from(fill);
|
|
388
406
|
// An empty fill cannot pad, so the value passes through, matching PostgreSQL.
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
padding.push(filler[padding.length % filler.length] ?? "");
|
|
394
|
-
}
|
|
395
|
-
return protectedSqlTextValue(name === "LPAD" ? padding.join("") + text.join("") : text.join("") + padding.join(""));
|
|
407
|
+
const padding = repeatedCodePointPrefix(fill, width - text.count);
|
|
408
|
+
if (padding === "")
|
|
409
|
+
return protectedSqlTextValue(text.text);
|
|
410
|
+
return boundedScalarResult(name === "LPAD" ? padding + text.text : text.text + padding, `${name} result`);
|
|
396
411
|
}
|
|
397
412
|
case "OVERLAY": {
|
|
398
413
|
// OVERLAY(s PLACING r FROM start [FOR length]) replaces `length` characters of `s`
|
|
@@ -402,8 +417,12 @@ export function scalarFunctionValue(name, values) {
|
|
|
402
417
|
return null;
|
|
403
418
|
}
|
|
404
419
|
}
|
|
405
|
-
const
|
|
406
|
-
const
|
|
420
|
+
const sourceText = stringArgument("OVERLAY", first);
|
|
421
|
+
const replacementText = stringArgument("OVERLAY", values[1]);
|
|
422
|
+
assertScalarInputLength(sourceText, "OVERLAY source");
|
|
423
|
+
assertScalarInputLength(replacementText, "OVERLAY replacement");
|
|
424
|
+
const text = Array.from(sourceText);
|
|
425
|
+
const replacement = Array.from(replacementText);
|
|
407
426
|
const start = numeric(values[2]);
|
|
408
427
|
if (!Number.isInteger(start) || start < 1) {
|
|
409
428
|
throw new TypeError("OVERLAY start must be a positive integer");
|
|
@@ -412,11 +431,11 @@ export function scalarFunctionValue(name, values) {
|
|
|
412
431
|
if (!Number.isInteger(span) || span < 0) {
|
|
413
432
|
throw new TypeError("OVERLAY length must be a non-negative integer");
|
|
414
433
|
}
|
|
415
|
-
return
|
|
434
|
+
return boundedScalarResult([
|
|
416
435
|
...text.slice(0, start - 1),
|
|
417
436
|
...replacement,
|
|
418
437
|
...text.slice(Math.min(start - 1 + span, text.length)),
|
|
419
|
-
].join(""));
|
|
438
|
+
].join(""), "OVERLAY result");
|
|
420
439
|
}
|
|
421
440
|
case "CAST":
|
|
422
441
|
return castValue(first, typeof values[1] === "string" ? values[1] : "");
|
|
@@ -424,13 +443,13 @@ export function scalarFunctionValue(name, values) {
|
|
|
424
443
|
// PostgreSQL SUBSTRING returns the characters whose positions fall in both the requested
|
|
425
444
|
// window and the string, so a start before 1 shortens the result instead of shifting it,
|
|
426
445
|
// and a window entirely off the string is empty rather than an error.
|
|
427
|
-
const
|
|
446
|
+
const source = stringArgument("SUBSTR", first);
|
|
428
447
|
if (values[1] === null || values[1] === undefined)
|
|
429
448
|
return null;
|
|
430
449
|
const start = numeric(values[1]);
|
|
431
450
|
if (!Number.isInteger(start))
|
|
432
451
|
throw new TypeError("SUBSTR start must be an integer");
|
|
433
|
-
let until =
|
|
452
|
+
let until = Number.POSITIVE_INFINITY;
|
|
434
453
|
if (values.length > 2) {
|
|
435
454
|
if (values[2] === null || values[2] === undefined)
|
|
436
455
|
return null;
|
|
@@ -441,9 +460,109 @@ export function scalarFunctionValue(name, values) {
|
|
|
441
460
|
until = start + length;
|
|
442
461
|
}
|
|
443
462
|
const from = Math.max(start, 1);
|
|
444
|
-
|
|
445
|
-
|
|
463
|
+
if (until <= from)
|
|
464
|
+
return "";
|
|
465
|
+
return boundedScalarResult(codePointSlice(source, from - 1, until - 1), "SUBSTR result");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function assertScalarInputLength(value, label) {
|
|
470
|
+
if (value.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
471
|
+
throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function boundedScalarResult(value, label) {
|
|
475
|
+
if (value.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
476
|
+
throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
477
|
+
}
|
|
478
|
+
return protectedSqlTextValue(value);
|
|
479
|
+
}
|
|
480
|
+
function codePointLength(value, end = value.length) {
|
|
481
|
+
let count = 0;
|
|
482
|
+
for (let index = 0; index < end; index += 1) {
|
|
483
|
+
const first = value.charCodeAt(index);
|
|
484
|
+
if (first >= 0xd800 && first <= 0xdbff && index + 1 < end) {
|
|
485
|
+
const second = value.charCodeAt(index + 1);
|
|
486
|
+
if (second >= 0xdc00 && second <= 0xdfff)
|
|
487
|
+
index += 1;
|
|
488
|
+
}
|
|
489
|
+
count += 1;
|
|
490
|
+
}
|
|
491
|
+
return count;
|
|
492
|
+
}
|
|
493
|
+
function codePointPrefix(value, count) {
|
|
494
|
+
let index = 0;
|
|
495
|
+
let found = 0;
|
|
496
|
+
while (index < value.length && found < count) {
|
|
497
|
+
const first = value.charCodeAt(index);
|
|
498
|
+
index +=
|
|
499
|
+
first >= 0xd800 &&
|
|
500
|
+
first <= 0xdbff &&
|
|
501
|
+
index + 1 < value.length &&
|
|
502
|
+
value.charCodeAt(index + 1) >= 0xdc00 &&
|
|
503
|
+
value.charCodeAt(index + 1) <= 0xdfff
|
|
504
|
+
? 2
|
|
505
|
+
: 1;
|
|
506
|
+
found += 1;
|
|
507
|
+
}
|
|
508
|
+
return { text: value.slice(0, index), count: found };
|
|
509
|
+
}
|
|
510
|
+
function repeatedCodePointPrefix(value, count) {
|
|
511
|
+
if (count <= 0 || value.length === 0)
|
|
512
|
+
return "";
|
|
513
|
+
const first = codePointPrefix(value, count);
|
|
514
|
+
if (first.count >= count)
|
|
515
|
+
return first.text;
|
|
516
|
+
const copies = Math.floor(count / first.count);
|
|
517
|
+
const remainder = count % first.count;
|
|
518
|
+
return first.text.repeat(copies) + codePointPrefix(first.text, remainder).text;
|
|
519
|
+
}
|
|
520
|
+
function codePointSlice(value, from, to) {
|
|
521
|
+
let codePoint = 0;
|
|
522
|
+
let index = 0;
|
|
523
|
+
let start = value.length;
|
|
524
|
+
let end = value.length;
|
|
525
|
+
while (index < value.length) {
|
|
526
|
+
if (codePoint === from)
|
|
527
|
+
start = index;
|
|
528
|
+
if (codePoint === to) {
|
|
529
|
+
end = index;
|
|
530
|
+
break;
|
|
446
531
|
}
|
|
532
|
+
const first = value.charCodeAt(index);
|
|
533
|
+
index +=
|
|
534
|
+
first >= 0xd800 &&
|
|
535
|
+
first <= 0xdbff &&
|
|
536
|
+
index + 1 < value.length &&
|
|
537
|
+
value.charCodeAt(index + 1) >= 0xdc00 &&
|
|
538
|
+
value.charCodeAt(index + 1) <= 0xdfff
|
|
539
|
+
? 2
|
|
540
|
+
: 1;
|
|
541
|
+
codePoint += 1;
|
|
542
|
+
}
|
|
543
|
+
if (codePoint < from)
|
|
544
|
+
return "";
|
|
545
|
+
if (to === Number.POSITIVE_INFINITY)
|
|
546
|
+
end = value.length;
|
|
547
|
+
const length = end - start;
|
|
548
|
+
if (length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
549
|
+
throw new RangeError(`SUBSTR result exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
550
|
+
}
|
|
551
|
+
return value.slice(start, end);
|
|
552
|
+
}
|
|
553
|
+
function assertReplacementResultLength(source, search, replacement) {
|
|
554
|
+
let matches = 0;
|
|
555
|
+
let from = 0;
|
|
556
|
+
for (;;) {
|
|
557
|
+
const index = source.indexOf(search, from);
|
|
558
|
+
if (index < 0)
|
|
559
|
+
break;
|
|
560
|
+
matches += 1;
|
|
561
|
+
from = index + search.length;
|
|
562
|
+
}
|
|
563
|
+
const resultLength = source.length + matches * (replacement.length - search.length);
|
|
564
|
+
if (!Number.isSafeInteger(resultLength) || resultLength > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
565
|
+
throw new RangeError(`REPLACE result exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
447
566
|
}
|
|
448
567
|
}
|
|
449
568
|
export const dateTruncUnits = new Set([
|
|
@@ -508,15 +627,15 @@ export function dateAddValue(value, months, milliseconds) {
|
|
|
508
627
|
if (!(value instanceof Date))
|
|
509
628
|
throw new TypeError("Date arithmetic requires a datetime value");
|
|
510
629
|
const monthCount = Number(months ?? 0);
|
|
511
|
-
const shifted =
|
|
630
|
+
const shifted = copyDate(value);
|
|
512
631
|
if (monthCount !== 0) {
|
|
513
|
-
const day = shifted
|
|
514
|
-
shifted
|
|
515
|
-
shifted
|
|
516
|
-
const lastDay = new Date(Date.UTC(shifted
|
|
517
|
-
shifted
|
|
632
|
+
const day = dateUtcDate(shifted);
|
|
633
|
+
setDateUtcDate(shifted, 1);
|
|
634
|
+
setDateUtcMonth(shifted, dateUtcMonth(shifted) + monthCount);
|
|
635
|
+
const lastDay = new Date(Date.UTC(dateUtcFullYear(shifted), dateUtcMonth(shifted) + 1, 0));
|
|
636
|
+
setDateUtcDate(shifted, Math.min(day, dateUtcDate(lastDay)));
|
|
518
637
|
}
|
|
519
|
-
return new Date(shifted
|
|
638
|
+
return new Date(dateMilliseconds(shifted) + Number(milliseconds ?? 0));
|
|
520
639
|
}
|
|
521
640
|
export function dateTruncValue(unit, value) {
|
|
522
641
|
if (typeof unit !== "string" || !dateTruncUnits.has(unit.toLowerCase())) {
|
|
@@ -527,9 +646,9 @@ export function dateTruncValue(unit, value) {
|
|
|
527
646
|
if (!(value instanceof Date))
|
|
528
647
|
throw new TypeError("DATE_TRUNC requires a datetime value");
|
|
529
648
|
const normalized = unit.toLowerCase();
|
|
530
|
-
const year = value
|
|
531
|
-
const month = value
|
|
532
|
-
const day = value
|
|
649
|
+
const year = dateUtcFullYear(value);
|
|
650
|
+
const month = dateUtcMonth(value);
|
|
651
|
+
const day = dateUtcDate(value);
|
|
533
652
|
switch (normalized) {
|
|
534
653
|
case "year":
|
|
535
654
|
return new Date(Date.UTC(year, 0, 1));
|
|
@@ -539,17 +658,17 @@ export function dateTruncValue(unit, value) {
|
|
|
539
658
|
return new Date(Date.UTC(year, month, 1));
|
|
540
659
|
case "week": {
|
|
541
660
|
const start = new Date(Date.UTC(year, month, day));
|
|
542
|
-
start
|
|
661
|
+
setDateUtcDate(start, dateUtcDate(start) - ((dateUtcDay(start) + 6) % 7));
|
|
543
662
|
return start;
|
|
544
663
|
}
|
|
545
664
|
case "day":
|
|
546
665
|
return new Date(Date.UTC(year, month, day));
|
|
547
666
|
case "hour":
|
|
548
|
-
return new Date(Date.UTC(year, month, day, value
|
|
667
|
+
return new Date(Date.UTC(year, month, day, dateUtcHours(value)));
|
|
549
668
|
case "minute":
|
|
550
|
-
return new Date(Date.UTC(year, month, day, value
|
|
669
|
+
return new Date(Date.UTC(year, month, day, dateUtcHours(value), dateUtcMinutes(value)));
|
|
551
670
|
default:
|
|
552
|
-
return new Date(Date.UTC(year, month, day, value
|
|
671
|
+
return new Date(Date.UTC(year, month, day, dateUtcHours(value), dateUtcMinutes(value), dateUtcSeconds(value)));
|
|
553
672
|
}
|
|
554
673
|
}
|
|
555
674
|
/** The output column type of one window: rankings and most aggregates count, MIN/MAX carry. */
|
|
@@ -693,6 +812,7 @@ function throwLocated(error, offset, span) {
|
|
|
693
812
|
throw error;
|
|
694
813
|
}
|
|
695
814
|
export function compileQuery(sql, options = {}) {
|
|
815
|
+
validateSqlSource(sql);
|
|
696
816
|
const { text, offset } = normalizeSql(sql);
|
|
697
817
|
if (text.length === 0)
|
|
698
818
|
throw new SqlCompileError("Enter a SELECT query", offset, 0);
|
|
@@ -737,7 +857,7 @@ function columnDefaultFor(expression, sql) {
|
|
|
737
857
|
return { kind: "literal", value };
|
|
738
858
|
}
|
|
739
859
|
if (value instanceof Date)
|
|
740
|
-
return { kind: "literal", value:
|
|
860
|
+
return { kind: "literal", value: copyDate(value) };
|
|
741
861
|
}
|
|
742
862
|
return { kind: "expression", sql };
|
|
743
863
|
}
|
|
@@ -996,7 +1116,7 @@ function parseCreateTrigger(text, tokens) {
|
|
|
996
1116
|
if (compiled.kind === "insert" && compiled.onConflict !== undefined) {
|
|
997
1117
|
throw new TypeError("Trigger body INSERTs cannot carry ON CONFLICT");
|
|
998
1118
|
}
|
|
999
|
-
if (compiled.
|
|
1119
|
+
if (compiled.returning !== undefined) {
|
|
1000
1120
|
throw new TypeError("Trigger bodies cannot carry RETURNING");
|
|
1001
1121
|
}
|
|
1002
1122
|
if ((compiled.parameterCount ?? 0) !== bindings.length) {
|
|
@@ -1015,6 +1135,7 @@ function parseCreateTrigger(text, tokens) {
|
|
|
1015
1135
|
* leading keyword fails explicitly.
|
|
1016
1136
|
*/
|
|
1017
1137
|
export function compileStatement(sql) {
|
|
1138
|
+
validateSqlSource(sql);
|
|
1018
1139
|
const { text, offset } = normalizeSql(sql);
|
|
1019
1140
|
if (text.length === 0)
|
|
1020
1141
|
throw new SqlCompileError("Enter a SQL statement", offset, 0);
|
|
@@ -1378,7 +1499,7 @@ function validateParameters(count, params) {
|
|
|
1378
1499
|
return;
|
|
1379
1500
|
}
|
|
1380
1501
|
if (value instanceof Date) {
|
|
1381
|
-
if (!Number.isFinite(value
|
|
1502
|
+
if (!Number.isFinite(dateMilliseconds(value))) {
|
|
1382
1503
|
throw new TypeError(`Parameter ${label} must be a valid date`);
|
|
1383
1504
|
}
|
|
1384
1505
|
return;
|
|
@@ -1500,7 +1621,7 @@ function cloneTreeValue(value) {
|
|
|
1500
1621
|
if (typeof value !== "object" || value === null)
|
|
1501
1622
|
return value;
|
|
1502
1623
|
if (value instanceof Date)
|
|
1503
|
-
return
|
|
1624
|
+
return copyDate(value);
|
|
1504
1625
|
if (Array.isArray(value)) {
|
|
1505
1626
|
const copy = new Array(value.length);
|
|
1506
1627
|
for (let index = 0; index < value.length; index += 1)
|
|
@@ -2105,6 +2226,7 @@ function trimPreparedResults(prepared, trim) {
|
|
|
2105
2226
|
},
|
|
2106
2227
|
execute: () => trim(prepared.execute()),
|
|
2107
2228
|
executeAsync: async (options) => trim(await prepared.executeAsync(options)),
|
|
2229
|
+
executeBatches: (options, consume) => prepared.executeBatches(options, (batch) => consume(trim(batch))),
|
|
2108
2230
|
close: () => {
|
|
2109
2231
|
prepared.close();
|
|
2110
2232
|
},
|
|
@@ -2129,21 +2251,25 @@ export function createPreparedQuery(plan, tables, options = {}) {
|
|
|
2129
2251
|
// Every engine entry expands MATCH(*) exactly once, here against the row tables' own
|
|
2130
2252
|
// columns; past this point no executor sees the "*" sentinel.
|
|
2131
2253
|
plan = expandFtsColumns(plan, rowTableSearchableColumns(tables));
|
|
2132
|
-
const resolution = subqueryResolutionSteps(plan);
|
|
2133
|
-
for (const step of resolution.steps)
|
|
2134
|
-
step.substitute(executeRowQueryInternal(step.block, tables));
|
|
2135
|
-
plan = resolution.plan;
|
|
2136
|
-
tables = resolveDerivedRowTables(plan, tables);
|
|
2137
|
-
plan = expandDistinctWildcard(plan, wildcardRowColumns(tables));
|
|
2138
2254
|
const memory = new QueryMemoryContext(options.executionMemoryBudgetBytes);
|
|
2139
|
-
if ([...tables.values()].some((rows) => rows.length === 0)) {
|
|
2140
|
-
if (options.executionMemoryBudgetBytes !== undefined) {
|
|
2141
|
-
memory.close();
|
|
2142
|
-
throw new TypeError("Query memory budgets require typed columnar schemas when an input table is empty");
|
|
2143
|
-
}
|
|
2144
|
-
return createPreparedRowQuery(plan, tables, memory);
|
|
2145
|
-
}
|
|
2146
2255
|
try {
|
|
2256
|
+
// Scalar subqueries and derived row sources are materialized before the vector plan is
|
|
2257
|
+
// prepared. They must share the same context: otherwise a tiny (or the finite default)
|
|
2258
|
+
// budget would apply only after an arbitrarily large intermediate had already escaped the
|
|
2259
|
+
// memory model.
|
|
2260
|
+
const resolution = subqueryResolutionSteps(plan);
|
|
2261
|
+
for (const step of resolution.steps) {
|
|
2262
|
+
step.substitute(executeRowQueryInternal(step.block, tables, memory));
|
|
2263
|
+
}
|
|
2264
|
+
plan = resolution.plan;
|
|
2265
|
+
tables = resolveDerivedRowTables(plan, tables, memory);
|
|
2266
|
+
plan = expandDistinctWildcard(plan, wildcardRowColumns(tables));
|
|
2267
|
+
if ([...tables.values()].some((rows) => rows.length === 0)) {
|
|
2268
|
+
if (options.executionMemoryBudgetBytes !== undefined) {
|
|
2269
|
+
throw new TypeError("Query memory budgets require typed columnar schemas when an input table is empty");
|
|
2270
|
+
}
|
|
2271
|
+
return createPreparedRowQuery(plan, tables, memory);
|
|
2272
|
+
}
|
|
2147
2273
|
return trimPreparedResults(createPreparedColumnarQuery(plan, normalizeColumnarTables(plan, tables), memory), externalizeQueryResult);
|
|
2148
2274
|
}
|
|
2149
2275
|
catch (error) {
|
|
@@ -2196,6 +2322,11 @@ export function createPreparedColumnarQuery(plan, tables, memory = new QueryMemo
|
|
|
2196
2322
|
throw new Error("Prepared query is closed");
|
|
2197
2323
|
return markExternalizationState(await prepared.executeAsync(options), outputNeedsExternalization);
|
|
2198
2324
|
},
|
|
2325
|
+
executeBatches(options, consume) {
|
|
2326
|
+
if (closed || prepared === undefined)
|
|
2327
|
+
throw new Error("Prepared query is closed");
|
|
2328
|
+
return prepared.executeBatches(options, (batch) => consume(markExternalizationState(batch, outputNeedsExternalization)));
|
|
2329
|
+
},
|
|
2199
2330
|
close() {
|
|
2200
2331
|
if (closed)
|
|
2201
2332
|
return;
|
|
@@ -2218,12 +2349,47 @@ function createPreparedRowQuery(plan, tables, memory) {
|
|
|
2218
2349
|
execute() {
|
|
2219
2350
|
if (closed || rows === undefined)
|
|
2220
2351
|
throw new Error("Prepared query is closed");
|
|
2221
|
-
|
|
2352
|
+
const executionMemory = memory.createChild();
|
|
2353
|
+
try {
|
|
2354
|
+
return externalizeQueryResult(executeRowQueryInternal(plan, cloneRowTables(rows), executionMemory));
|
|
2355
|
+
}
|
|
2356
|
+
finally {
|
|
2357
|
+
executionMemory.close();
|
|
2358
|
+
}
|
|
2222
2359
|
},
|
|
2223
2360
|
async executeAsync() {
|
|
2224
2361
|
if (closed || rows === undefined)
|
|
2225
2362
|
throw new Error("Prepared query is closed");
|
|
2226
|
-
|
|
2363
|
+
const executionMemory = memory.createChild();
|
|
2364
|
+
try {
|
|
2365
|
+
return externalizeQueryResult(executeRowQueryInternal(plan, cloneRowTables(rows), executionMemory));
|
|
2366
|
+
}
|
|
2367
|
+
finally {
|
|
2368
|
+
executionMemory.close();
|
|
2369
|
+
}
|
|
2370
|
+
},
|
|
2371
|
+
async executeBatches(options, consume) {
|
|
2372
|
+
if (closed || rows === undefined)
|
|
2373
|
+
throw new Error("Prepared query is closed");
|
|
2374
|
+
if (!Number.isSafeInteger(options.batchRows) || options.batchRows <= 0) {
|
|
2375
|
+
throw new RangeError("Query batch rows must be a positive whole number");
|
|
2376
|
+
}
|
|
2377
|
+
const executionMemory = memory.createChild();
|
|
2378
|
+
let result;
|
|
2379
|
+
try {
|
|
2380
|
+
result = externalizeQueryResult(executeRowQueryInternal(plan, cloneRowTables(rows), executionMemory));
|
|
2381
|
+
}
|
|
2382
|
+
finally {
|
|
2383
|
+
executionMemory.close();
|
|
2384
|
+
}
|
|
2385
|
+
for (let start = 0; start < result.rows.length; start += options.batchRows) {
|
|
2386
|
+
options.signal?.throwIfAborted();
|
|
2387
|
+
await consume({
|
|
2388
|
+
columns: [...result.columns],
|
|
2389
|
+
rows: result.rows.slice(start, start + options.batchRows),
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2392
|
+
return result.columns;
|
|
2227
2393
|
},
|
|
2228
2394
|
close() {
|
|
2229
2395
|
closed = true;
|
|
@@ -2238,7 +2404,7 @@ function cloneRowTables(tables, protectText = false) {
|
|
|
2238
2404
|
rows.map((row) => Object.fromEntries(Object.entries(row).map(([column, value]) => [
|
|
2239
2405
|
column,
|
|
2240
2406
|
value instanceof Date
|
|
2241
|
-
?
|
|
2407
|
+
? copyDate(value)
|
|
2242
2408
|
: protectText && typeof value === "string"
|
|
2243
2409
|
? protectedSqlTextValue(value)
|
|
2244
2410
|
: value,
|
|
@@ -2519,10 +2685,10 @@ export function mapBlockExpressions(block, map) {
|
|
|
2519
2685
|
export function resolveStatementDatetimes(plan, now = new Date()) {
|
|
2520
2686
|
if (plan.usesStatementDatetime !== true)
|
|
2521
2687
|
return plan;
|
|
2522
|
-
const iso = now
|
|
2688
|
+
const iso = dateIsoString(now);
|
|
2523
2689
|
const values = new Map([
|
|
2524
2690
|
["CURRENT_DATE", new Date(`${iso.slice(0, 10)}T00:00:00.000Z`)],
|
|
2525
|
-
["CURRENT_TIMESTAMP",
|
|
2691
|
+
["CURRENT_TIMESTAMP", copyDate(now)],
|
|
2526
2692
|
["LOCALTIME", iso.slice(11, 19)],
|
|
2527
2693
|
]);
|
|
2528
2694
|
const resolved = clonePlanTree(plan);
|
|
@@ -3111,7 +3277,7 @@ export function applyWindowFunctions(result, windows, options = {}) {
|
|
|
3111
3277
|
};
|
|
3112
3278
|
}
|
|
3113
3279
|
/** Executes each derived or set-operation source with the row reference. */
|
|
3114
|
-
function resolveDerivedRowTables(plan, tables) {
|
|
3280
|
+
function resolveDerivedRowTables(plan, tables, memory) {
|
|
3115
3281
|
const sources = [plan.base, ...plan.joins];
|
|
3116
3282
|
const needsDual = sources.some((source) => source.derived === undefined && source.table === DUAL_TABLE);
|
|
3117
3283
|
if (!needsDual &&
|
|
@@ -3127,28 +3293,28 @@ function resolveDerivedRowTables(plan, tables) {
|
|
|
3127
3293
|
for (const source of sources) {
|
|
3128
3294
|
if (source.recursive !== undefined) {
|
|
3129
3295
|
const { reference, base, step, all } = source.recursive;
|
|
3130
|
-
const state = createRecursiveCteState(executeRowQueryInternal(base, tables), all);
|
|
3296
|
+
const state = createRecursiveCteState(executeRowQueryInternal(base, tables, memory), all);
|
|
3131
3297
|
while (state.frontier.length > 0) {
|
|
3132
3298
|
const stepTables = new Map(tables);
|
|
3133
3299
|
stepTables.set(reference, state.frontier);
|
|
3134
|
-
state.absorb(executeRowQueryInternal(step, stepTables));
|
|
3300
|
+
state.absorb(executeRowQueryInternal(step, stepTables, memory));
|
|
3135
3301
|
}
|
|
3136
3302
|
resolved.set(source.table, state.rows);
|
|
3137
3303
|
continue;
|
|
3138
3304
|
}
|
|
3139
3305
|
if (source.union !== undefined) {
|
|
3140
|
-
const results = source.union.blocks.map((block) => executeRowQueryInternal(block, tables));
|
|
3306
|
+
const results = source.union.blocks.map((block) => executeRowQueryInternal(block, tables, memory));
|
|
3141
3307
|
resolved.set(source.table, combineUnionResults(results, source.union.ops).rows);
|
|
3142
3308
|
continue;
|
|
3143
3309
|
}
|
|
3144
3310
|
if (source.windowed !== undefined) {
|
|
3145
|
-
const inner = executeRowQueryInternal(source.windowed.block, tables);
|
|
3311
|
+
const inner = executeRowQueryInternal(source.windowed.block, tables, memory);
|
|
3146
3312
|
resolved.set(source.table, applyWindowFunctions(inner, source.windowed.windows).rows);
|
|
3147
3313
|
continue;
|
|
3148
3314
|
}
|
|
3149
3315
|
if (source.derived === undefined)
|
|
3150
3316
|
continue;
|
|
3151
|
-
resolved.set(source.table, executeRowQueryInternal(source.derived, tables).rows);
|
|
3317
|
+
resolved.set(source.table, executeRowQueryInternal(source.derived, tables, memory).rows);
|
|
3152
3318
|
}
|
|
3153
3319
|
return resolved;
|
|
3154
3320
|
}
|
|
@@ -3170,7 +3336,7 @@ function normalizeColumnarTables(plan, tables) {
|
|
|
3170
3336
|
export function executeRowQuery(plan, tables) {
|
|
3171
3337
|
return externalizeQueryResult(executeRowQueryInternal(plan, cloneRowTables(tables, true)));
|
|
3172
3338
|
}
|
|
3173
|
-
function executeRowQueryInternal(plan, tables) {
|
|
3339
|
+
function executeRowQueryInternal(plan, tables, memory) {
|
|
3174
3340
|
assertTailParametersBound(plan);
|
|
3175
3341
|
validateGrouping(plan);
|
|
3176
3342
|
plan = resolveStatementDatetimes(plan);
|
|
@@ -3182,7 +3348,7 @@ function executeRowQueryInternal(plan, tables) {
|
|
|
3182
3348
|
plan = expandFtsColumns(plan, rowTableSearchableColumns(tables));
|
|
3183
3349
|
const resolution = subqueryResolutionSteps(plan);
|
|
3184
3350
|
for (const step of resolution.steps) {
|
|
3185
|
-
step.substitute(executeRowQueryInternal(step.block, tables));
|
|
3351
|
+
step.substitute(executeRowQueryInternal(step.block, tables, memory));
|
|
3186
3352
|
}
|
|
3187
3353
|
// Stats annotation writes into the plan; when resolution had nothing to clone, the plan is
|
|
3188
3354
|
// still the caller's (possibly cached) object, and frozen statistics would survive into later
|
|
@@ -3191,7 +3357,7 @@ function executeRowQueryInternal(plan, tables) {
|
|
|
3191
3357
|
resolution.plan === plan && planContainsFts(plan, "bm25")
|
|
3192
3358
|
? structuredClone(plan)
|
|
3193
3359
|
: resolution.plan;
|
|
3194
|
-
tables = resolveDerivedRowTables(plan, tables);
|
|
3360
|
+
tables = resolveDerivedRowTables(plan, tables, memory);
|
|
3195
3361
|
plan = expandDistinctWildcard(plan, wildcardRowColumns(tables));
|
|
3196
3362
|
annotateRowFtsStats(plan, tables);
|
|
3197
3363
|
let contexts = (tables.get(plan.base.table) ?? []).map((row) => ({
|
|
@@ -3212,17 +3378,28 @@ function executeRowQueryInternal(plan, tables) {
|
|
|
3212
3378
|
group.push(context);
|
|
3213
3379
|
groups.set(key, group);
|
|
3214
3380
|
}
|
|
3215
|
-
rows = [
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3381
|
+
rows = [];
|
|
3382
|
+
for (const group of groups.values()) {
|
|
3383
|
+
if (!plan.having.every((predicate) => evaluateBooleanExpression({
|
|
3384
|
+
kind: "condition",
|
|
3385
|
+
operator: predicate.operator,
|
|
3386
|
+
left: predicate.left,
|
|
3387
|
+
right: predicate.right,
|
|
3388
|
+
}, (nested) => evaluate(nested, group[0] ?? {}, group)) === true)) {
|
|
3389
|
+
continue;
|
|
3390
|
+
}
|
|
3391
|
+
const row = project(plan.select, group[0] ?? {}, group);
|
|
3392
|
+
memory?.tally(rowQueryPayloadBytes(row), "Row query result");
|
|
3393
|
+
rows.push(row);
|
|
3394
|
+
}
|
|
3223
3395
|
}
|
|
3224
3396
|
else {
|
|
3225
|
-
rows =
|
|
3397
|
+
rows = [];
|
|
3398
|
+
for (const context of contexts) {
|
|
3399
|
+
const row = project(plan.select, context);
|
|
3400
|
+
memory?.tally(rowQueryPayloadBytes(row), "Row query result");
|
|
3401
|
+
rows.push(row);
|
|
3402
|
+
}
|
|
3226
3403
|
}
|
|
3227
3404
|
if (plan.orderBy.length > 0) {
|
|
3228
3405
|
// Only a wildcard select needs the source shapes: every other select resolves against its
|
|
@@ -3260,6 +3437,25 @@ function executeRowQueryInternal(plan, tables) {
|
|
|
3260
3437
|
: plan.select.map((item) => item.alias);
|
|
3261
3438
|
return ties.trim({ columns, rows });
|
|
3262
3439
|
}
|
|
3440
|
+
/** Matches the vector executor's deliberately inexpensive modeled result-payload accounting. */
|
|
3441
|
+
function rowQueryPayloadBytes(row) {
|
|
3442
|
+
let total = 8;
|
|
3443
|
+
for (const key in row) {
|
|
3444
|
+
const value = row[key] ?? null;
|
|
3445
|
+
total +=
|
|
3446
|
+
value === null
|
|
3447
|
+
? 1
|
|
3448
|
+
: typeof value === "boolean"
|
|
3449
|
+
? 2
|
|
3450
|
+
: typeof value === "number" || value instanceof Date
|
|
3451
|
+
? 9
|
|
3452
|
+
: 1 + value.length;
|
|
3453
|
+
}
|
|
3454
|
+
if (!Number.isSafeInteger(total) || total < 0) {
|
|
3455
|
+
throw new RangeError("Row query result payload exceeds the safe integer range");
|
|
3456
|
+
}
|
|
3457
|
+
return total;
|
|
3458
|
+
}
|
|
3263
3459
|
/**
|
|
3264
3460
|
* Resolves one ORDER BY reference to the output column that carries its values, throwing when
|
|
3265
3461
|
* nothing matches. Dropping an unresolved sort key silently would return rows in an arbitrary
|
|
@@ -3532,7 +3728,7 @@ function evaluate(expression, context, group) {
|
|
|
3532
3728
|
if (expression.distinct === true) {
|
|
3533
3729
|
const seen = new Set();
|
|
3534
3730
|
values = values.filter((value) => {
|
|
3535
|
-
const key = value instanceof Date ? ` d${String(value
|
|
3731
|
+
const key = value instanceof Date ? ` d${String(dateMilliseconds(value))}` : value;
|
|
3536
3732
|
if (seen.has(key))
|
|
3537
3733
|
return false;
|
|
3538
3734
|
seen.add(key);
|
|
@@ -3664,10 +3860,6 @@ export function cachedListMembership(node, items) {
|
|
|
3664
3860
|
}
|
|
3665
3861
|
return cached;
|
|
3666
3862
|
}
|
|
3667
|
-
/** Compiles a LIKE pattern (% = any run, _ = any character) to an anchored RegExp, cached. */
|
|
3668
|
-
export function likeRegExp(pattern, caseInsensitive = false, escape) {
|
|
3669
|
-
return compileLikePattern(pattern, caseInsensitive, escape);
|
|
3670
|
-
}
|
|
3671
3863
|
const extractFields = new Set([
|
|
3672
3864
|
"year",
|
|
3673
3865
|
"quarter",
|
|
@@ -3695,70 +3887,38 @@ function extractDatePart(field, value) {
|
|
|
3695
3887
|
throw new TypeError("EXTRACT requires a datetime value");
|
|
3696
3888
|
switch (normalized) {
|
|
3697
3889
|
case "year":
|
|
3698
|
-
return value
|
|
3890
|
+
return dateUtcFullYear(value);
|
|
3699
3891
|
case "quarter":
|
|
3700
|
-
return Math.floor(value
|
|
3892
|
+
return Math.floor(dateUtcMonth(value) / 3) + 1;
|
|
3701
3893
|
case "month":
|
|
3702
|
-
return value
|
|
3894
|
+
return dateUtcMonth(value) + 1;
|
|
3703
3895
|
case "week": {
|
|
3704
|
-
const date = new Date(Date.UTC(value
|
|
3896
|
+
const date = new Date(Date.UTC(dateUtcFullYear(value), dateUtcMonth(value), dateUtcDate(value)));
|
|
3705
3897
|
// ISO week: shift to the Thursday of this week, then count weeks from January 1st.
|
|
3706
|
-
date
|
|
3707
|
-
const yearStart = Date.UTC(date
|
|
3708
|
-
return Math.ceil(((date
|
|
3898
|
+
setDateUtcDate(date, dateUtcDate(date) + 4 - (dateUtcDay(date) || 7));
|
|
3899
|
+
const yearStart = Date.UTC(dateUtcFullYear(date), 0, 1);
|
|
3900
|
+
return Math.ceil(((dateMilliseconds(date) - yearStart) / 86_400_000 + 1) / 7);
|
|
3709
3901
|
}
|
|
3710
3902
|
case "day":
|
|
3711
|
-
return value
|
|
3903
|
+
return dateUtcDate(value);
|
|
3712
3904
|
case "hour":
|
|
3713
|
-
return value
|
|
3905
|
+
return dateUtcHours(value);
|
|
3714
3906
|
case "minute":
|
|
3715
|
-
return value
|
|
3907
|
+
return dateUtcMinutes(value);
|
|
3716
3908
|
case "second":
|
|
3717
|
-
return value
|
|
3909
|
+
return dateUtcSeconds(value);
|
|
3718
3910
|
case "epoch":
|
|
3719
|
-
return value
|
|
3911
|
+
return dateMilliseconds(value) / 1000;
|
|
3720
3912
|
default:
|
|
3721
|
-
return value
|
|
3913
|
+
return dateUtcDay(value);
|
|
3722
3914
|
}
|
|
3723
3915
|
}
|
|
3724
|
-
const likeMatcherCache = new Map();
|
|
3725
3916
|
/**
|
|
3726
|
-
*
|
|
3727
|
-
*
|
|
3728
|
-
* dominate real workloads — and everything else falls back to the anchored RegExp.
|
|
3917
|
+
* Shared bounded LIKE matcher. Compilation and the common literal prefix/suffix/containment
|
|
3918
|
+
* fast paths are cached in sql-semantics; complex wildcard shapes have deterministic work caps.
|
|
3729
3919
|
*/
|
|
3730
3920
|
export function likeMatches(pattern, value, caseInsensitive = false, escape) {
|
|
3731
|
-
|
|
3732
|
-
let matcher = likeMatcherCache.get(key);
|
|
3733
|
-
if (matcher === undefined) {
|
|
3734
|
-
matcher = buildLikeMatcher(pattern, caseInsensitive, escape);
|
|
3735
|
-
if (likeMatcherCache.size >= 128)
|
|
3736
|
-
likeMatcherCache.clear();
|
|
3737
|
-
likeMatcherCache.set(key, matcher);
|
|
3738
|
-
}
|
|
3739
|
-
return matcher(value);
|
|
3740
|
-
}
|
|
3741
|
-
function buildLikeMatcher(pattern, caseInsensitive, escape) {
|
|
3742
|
-
if (escape === undefined && !pattern.includes("_")) {
|
|
3743
|
-
const leading = pattern.startsWith("%");
|
|
3744
|
-
const trailing = pattern.endsWith("%");
|
|
3745
|
-
const body = pattern.slice(leading ? 1 : 0, trailing ? pattern.length - 1 : undefined);
|
|
3746
|
-
if (!body.includes("%")) {
|
|
3747
|
-
const needle = caseInsensitive ? body.toLowerCase() : body;
|
|
3748
|
-
const fold = caseInsensitive
|
|
3749
|
-
? (value) => value.toLowerCase()
|
|
3750
|
-
: (value) => value;
|
|
3751
|
-
if (leading && trailing)
|
|
3752
|
-
return (value) => fold(value).includes(needle);
|
|
3753
|
-
if (trailing)
|
|
3754
|
-
return (value) => fold(value).startsWith(needle);
|
|
3755
|
-
if (leading)
|
|
3756
|
-
return (value) => fold(value).endsWith(needle);
|
|
3757
|
-
return (value) => fold(value) === needle;
|
|
3758
|
-
}
|
|
3759
|
-
}
|
|
3760
|
-
const regExp = likeRegExp(pattern, caseInsensitive, escape);
|
|
3761
|
-
return (value) => regExp.test(value);
|
|
3921
|
+
return compileLikePattern(pattern, caseInsensitive, escape).test(value);
|
|
3762
3922
|
}
|
|
3763
3923
|
/** Splits a quantified operator like "> ANY" into its comparison and quantifier. */
|
|
3764
3924
|
export function parseQuantified(operator) {
|
|
@@ -4087,7 +4247,7 @@ export function expressionAliases(expression) {
|
|
|
4087
4247
|
return new Set(expressionColumns(expression).flatMap((reference) => reference.includes(".") ? [reference.split(".")[0] ?? ""] : []));
|
|
4088
4248
|
}
|
|
4089
4249
|
function comparable(value) {
|
|
4090
|
-
return value instanceof Date ? value
|
|
4250
|
+
return value instanceof Date ? dateMilliseconds(value) : value;
|
|
4091
4251
|
}
|
|
4092
4252
|
/**
|
|
4093
4253
|
* Resolves NULL placement for one order term. An omitted placement follows PostgreSQL: NULLS LAST
|
|
@@ -6529,6 +6689,9 @@ class Parser {
|
|
|
6529
6689
|
throw new TypeError("Use either ? or $n placeholders in one statement, not both");
|
|
6530
6690
|
}
|
|
6531
6691
|
this.#positionalParameters += 1;
|
|
6692
|
+
if (this.#positionalParameters > MAX_SQL_PARAMETERS) {
|
|
6693
|
+
throw new RangeError(`A SQL statement cannot exceed ${String(MAX_SQL_PARAMETERS)} parameters`);
|
|
6694
|
+
}
|
|
6532
6695
|
return { kind: "parameter", index: this.#positionalParameters - 1 };
|
|
6533
6696
|
}
|
|
6534
6697
|
if (this.#positionalParameters > 0) {
|
|
@@ -6538,6 +6701,9 @@ class Parser {
|
|
|
6538
6701
|
if (!Number.isInteger(number) || number < 1) {
|
|
6539
6702
|
throw new TypeError(`Parameter numbers start at $1: $${token.text}`);
|
|
6540
6703
|
}
|
|
6704
|
+
if (number > MAX_SQL_PARAMETERS) {
|
|
6705
|
+
throw new RangeError(`A SQL statement cannot exceed ${String(MAX_SQL_PARAMETERS)} parameters`);
|
|
6706
|
+
}
|
|
6541
6707
|
this.#highestNumberedParameter = Math.max(this.#highestNumberedParameter, number);
|
|
6542
6708
|
return { kind: "parameter", index: number - 1 };
|
|
6543
6709
|
}
|
|
@@ -6789,7 +6955,7 @@ class Parser {
|
|
|
6789
6955
|
}
|
|
6790
6956
|
if (upper === "DATE" && this.#peek().kind === "string") {
|
|
6791
6957
|
const date = new Date(`${this.#take("string").text}T00:00:00.000Z`);
|
|
6792
|
-
if (!Number.isFinite(date
|
|
6958
|
+
if (!Number.isFinite(dateMilliseconds(date)))
|
|
6793
6959
|
throw new TypeError("Invalid DATE literal");
|
|
6794
6960
|
return { kind: "literal", value: date };
|
|
6795
6961
|
}
|
|
@@ -8245,8 +8411,9 @@ function jsonTableColumnValue(column, value) {
|
|
|
8245
8411
|
date = new Date(value);
|
|
8246
8412
|
else
|
|
8247
8413
|
throw new TypeError("JSON_TABLE datetime is invalid");
|
|
8248
|
-
if (!Number.isFinite(date
|
|
8414
|
+
if (!Number.isFinite(dateMilliseconds(date))) {
|
|
8249
8415
|
throw new TypeError("JSON_TABLE datetime is invalid");
|
|
8416
|
+
}
|
|
8250
8417
|
return date;
|
|
8251
8418
|
}
|
|
8252
8419
|
export function timestampLiteral(text) {
|
|
@@ -8257,8 +8424,9 @@ export function timestampLiteral(text) {
|
|
|
8257
8424
|
const [, day, time = "00:00:00", zone = "Z"] = match;
|
|
8258
8425
|
const seconds = time.length === 5 ? `${time}:00` : time;
|
|
8259
8426
|
const date = new Date(`${String(day)}T${seconds}${zone === "Z" ? "Z" : zone}`);
|
|
8260
|
-
if (!Number.isFinite(date
|
|
8427
|
+
if (!Number.isFinite(dateMilliseconds(date))) {
|
|
8261
8428
|
throw new TypeError(`Invalid TIMESTAMP literal: ${text}`);
|
|
8429
|
+
}
|
|
8262
8430
|
return date;
|
|
8263
8431
|
}
|
|
8264
8432
|
/** The parser's OFFSET range contract, shared with the typed builder. */
|
|
@@ -8433,6 +8601,24 @@ function validNumericLiteral(text, radix) {
|
|
|
8433
8601
|
}
|
|
8434
8602
|
function tokenize(sql) {
|
|
8435
8603
|
const tokens = [];
|
|
8604
|
+
let nestingDepth = 0;
|
|
8605
|
+
const push = (token) => {
|
|
8606
|
+
if (tokens.length >= MAX_SQL_TOKENS) {
|
|
8607
|
+
throw new SqlCompileError(`A SQL statement cannot exceed ${String(MAX_SQL_TOKENS)} tokens`, token.start, Math.max(token.end - token.start, 1));
|
|
8608
|
+
}
|
|
8609
|
+
if (token.kind === "punctuation") {
|
|
8610
|
+
if (token.text === "(" || token.text === "[") {
|
|
8611
|
+
nestingDepth += 1;
|
|
8612
|
+
if (nestingDepth > MAX_SQL_NESTING_DEPTH) {
|
|
8613
|
+
throw new SqlCompileError(`SQL nesting cannot exceed ${String(MAX_SQL_NESTING_DEPTH)} levels`, token.start, 1);
|
|
8614
|
+
}
|
|
8615
|
+
}
|
|
8616
|
+
else if ((token.text === ")" || token.text === "]") && nestingDepth > 0) {
|
|
8617
|
+
nestingDepth -= 1;
|
|
8618
|
+
}
|
|
8619
|
+
}
|
|
8620
|
+
tokens.push(token);
|
|
8621
|
+
};
|
|
8436
8622
|
let index = 0;
|
|
8437
8623
|
while (index < sql.length) {
|
|
8438
8624
|
const character = sql[index] ?? "";
|
|
@@ -8444,7 +8630,7 @@ function tokenize(sql) {
|
|
|
8444
8630
|
const start = index++;
|
|
8445
8631
|
while (index < sql.length && /[A-Za-z0-9_]/.test(sql[index] ?? ""))
|
|
8446
8632
|
index += 1;
|
|
8447
|
-
|
|
8633
|
+
push({ kind: "identifier", text: sql.slice(start, index), start, end: index });
|
|
8448
8634
|
continue;
|
|
8449
8635
|
}
|
|
8450
8636
|
if (/\d/.test(character)) {
|
|
@@ -8461,7 +8647,7 @@ function tokenize(sql) {
|
|
|
8461
8647
|
if (!validNumericLiteral(digits, radix) || !Number.isSafeInteger(value)) {
|
|
8462
8648
|
throw new SqlCompileError(`Invalid number: ${sql.slice(start, index)}`, start, index - start);
|
|
8463
8649
|
}
|
|
8464
|
-
|
|
8650
|
+
push({ kind: "number", text: String(value), start, end: index });
|
|
8465
8651
|
continue;
|
|
8466
8652
|
}
|
|
8467
8653
|
index += 1;
|
|
@@ -8471,7 +8657,7 @@ function tokenize(sql) {
|
|
|
8471
8657
|
const text = sql.slice(start, index);
|
|
8472
8658
|
if (!validNumericLiteral(text, 10))
|
|
8473
8659
|
throw new SqlCompileError(`Invalid number: ${text}`, start, index - start);
|
|
8474
|
-
|
|
8660
|
+
push({ kind: "number", text: text.replaceAll("_", ""), start, end: index });
|
|
8475
8661
|
continue;
|
|
8476
8662
|
}
|
|
8477
8663
|
if (character === "'") {
|
|
@@ -8493,7 +8679,7 @@ function tokenize(sql) {
|
|
|
8493
8679
|
}
|
|
8494
8680
|
if (!closed)
|
|
8495
8681
|
throw new SqlCompileError("Unterminated string literal", start, sql.length - start);
|
|
8496
|
-
|
|
8682
|
+
push({ kind: "string", text: value, start, end: index });
|
|
8497
8683
|
continue;
|
|
8498
8684
|
}
|
|
8499
8685
|
if (character === '"') {
|
|
@@ -8519,11 +8705,11 @@ function tokenize(sql) {
|
|
|
8519
8705
|
if (value.length === 0) {
|
|
8520
8706
|
throw new SqlCompileError("Quoted identifiers cannot be empty", start, index - start);
|
|
8521
8707
|
}
|
|
8522
|
-
|
|
8708
|
+
push({ kind: "identifier", text: value, quoted: true, start, end: index });
|
|
8523
8709
|
continue;
|
|
8524
8710
|
}
|
|
8525
8711
|
if (character === "?") {
|
|
8526
|
-
|
|
8712
|
+
push({ kind: "parameter", text: "", start: index, end: index + 1 });
|
|
8527
8713
|
index += 1;
|
|
8528
8714
|
continue;
|
|
8529
8715
|
}
|
|
@@ -8535,7 +8721,7 @@ function tokenize(sql) {
|
|
|
8535
8721
|
if (digits.length === 0) {
|
|
8536
8722
|
throw new SqlCompileError("Expected a parameter number after $", start, 1);
|
|
8537
8723
|
}
|
|
8538
|
-
|
|
8724
|
+
push({ kind: "parameter", text: digits, start, end: index });
|
|
8539
8725
|
continue;
|
|
8540
8726
|
}
|
|
8541
8727
|
const pair = sql.slice(index, index + 2);
|
|
@@ -8557,19 +8743,19 @@ function tokenize(sql) {
|
|
|
8557
8743
|
if (character === ";") {
|
|
8558
8744
|
// Statement separators lex normally; the routers reject them everywhere except inside
|
|
8559
8745
|
// a CREATE TRIGGER body, which is the one multi-statement construct.
|
|
8560
|
-
|
|
8746
|
+
push({ kind: "punctuation", text: ";", start: index, end: index + 1 });
|
|
8561
8747
|
index += 1;
|
|
8562
8748
|
continue;
|
|
8563
8749
|
}
|
|
8564
8750
|
if ([">=", "<=", "!=", "<>", "||"].includes(pair)) {
|
|
8565
|
-
|
|
8751
|
+
push({ kind: "operator", text: pair, start: index, end: index + 2 });
|
|
8566
8752
|
index += 2;
|
|
8567
8753
|
continue;
|
|
8568
8754
|
}
|
|
8569
8755
|
if (["+", "-", "*", "/", "%", "=", ">", "<"].includes(character))
|
|
8570
|
-
|
|
8756
|
+
push({ kind: "operator", text: character, start: index, end: index + 1 });
|
|
8571
8757
|
else if (["(", ")", "[", "]", ",", "."].includes(character))
|
|
8572
|
-
|
|
8758
|
+
push({ kind: "punctuation", text: character, start: index, end: index + 1 });
|
|
8573
8759
|
else
|
|
8574
8760
|
throw new SqlCompileError(`Unsupported SQL character: ${character}`, index, 1);
|
|
8575
8761
|
index += 1;
|
|
@@ -8577,4 +8763,11 @@ function tokenize(sql) {
|
|
|
8577
8763
|
tokens.push({ kind: "eof", text: "", start: sql.length, end: sql.length });
|
|
8578
8764
|
return tokens;
|
|
8579
8765
|
}
|
|
8580
|
-
|
|
8766
|
+
function validateSqlSource(sql) {
|
|
8767
|
+
if (typeof sql !== "string")
|
|
8768
|
+
throw new TypeError("SQL must be a string");
|
|
8769
|
+
if (sql.length > MAX_SQL_TEXT_CHARACTERS) {
|
|
8770
|
+
throw new RangeError(`SQL text cannot exceed ${String(MAX_SQL_TEXT_CHARACTERS)} characters`);
|
|
8771
|
+
}
|
|
8772
|
+
assertWellFormedString(sql, "SQL text");
|
|
8773
|
+
}
|