@minnowdb/core 0.2.1 → 0.4.0
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 +23 -32
- 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 +2 -1
- package/dist/engine/artifact-cache.js +5 -1
- package/dist/engine/batch.d.ts +11 -3
- package/dist/engine/batch.js +17 -7
- 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 +24 -5
- package/dist/engine/catalog.js +34 -7
- package/dist/engine/client.d.ts +29 -7
- package/dist/engine/client.js +216 -40
- package/dist/engine/database.d.ts +224 -39
- package/dist/engine/database.js +7475 -1539
- package/dist/engine/defaults.d.ts +4 -8
- package/dist/engine/defaults.js +46 -22
- 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 +41 -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 -10
- package/dist/engine/index.js +12 -9
- 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 +504 -126
- 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 +262 -27
- package/dist/engine/query-api.d.ts +3 -0
- package/dist/engine/query-api.js +3 -0
- package/dist/engine/query-cache.d.ts +1 -2
- package/dist/engine/query-cache.js +7 -5
- package/dist/engine/query.d.ts +134 -29
- package/dist/engine/query.js +1668 -372
- package/dist/engine/result-wire.d.ts +0 -1
- package/dist/engine/result-wire.js +2 -2
- package/dist/engine/schema-wire.d.ts +6 -3
- package/dist/engine/schema-wire.js +40 -34
- package/dist/engine/schema.d.ts +156 -80
- package/dist/engine/schema.js +570 -108
- package/dist/engine/sort-keys.d.ts +4 -4
- package/dist/engine/sort-keys.js +44 -29
- package/dist/engine/sql-domains.d.ts +31 -0
- package/dist/engine/sql-domains.js +585 -0
- package/dist/engine/sql-driver.d.ts +19 -0
- package/dist/engine/sql-driver.js +1 -0
- package/dist/engine/sql-json.d.ts +7 -9
- package/dist/engine/sql-json.js +75 -15
- package/dist/engine/sql-semantics.d.ts +8 -3
- package/dist/engine/sql-semantics.js +458 -30
- package/dist/engine/typed-live.d.ts +67 -0
- package/dist/engine/typed-live.js +349 -0
- package/dist/engine/vector.d.ts +12 -2
- package/dist/engine/vector.js +635 -119
- package/dist/engine/worker-host.d.ts +9 -2
- package/dist/engine/worker-host.js +620 -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 +3 -4
- package/dist/plan/index.js +3 -4
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/storage/indexeddb.d.ts +99 -51
- package/dist/storage/indexeddb.js +13448 -2660
- package/dist/storage/memory.d.ts +64 -57
- package/dist/storage/memory.js +1042 -128
- 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 +409 -75
- package/dist/storage/opfs/leader.js +4621 -650
- package/dist/storage/opfs/rpc.d.ts +15 -3
- package/dist/storage/opfs/rpc.js +224 -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 +33 -99
- package/dist/storage/opfs/store.js +453 -364
- 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 -85
- package/dist/storage/snapshot.js +33 -265
- package/dist/storage/toolkit/extents.d.ts +41 -7
- package/dist/storage/toolkit/extents.js +402 -39
- package/dist/storage/toolkit/index.d.ts +4 -5
- package/dist/storage/toolkit/index.js +28 -4
- package/dist/storage/toolkit/record-core.d.ts +182 -55
- package/dist/storage/toolkit/record-core.js +6158 -1331
- 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 +15 -2
- package/dist/storage/toolkit/wire.js +199 -10
- package/dist/storage/types.d.ts +1439 -204
- package/dist/storage/types.js +1745 -141
- package/dist/testing/block-store-conformance.d.ts +0 -1
- package/dist/testing/block-store-conformance.js +1009 -119
- package/dist/testing/index.d.ts +46 -26
- package/dist/testing/index.js +112 -57
- package/dist/testing/opfs-shim.d.ts +13 -3
- package/dist/testing/opfs-shim.js +40 -6
- package/dist/testing/simulator.d.ts +97 -0
- package/dist/testing/simulator.js +591 -0
- package/dist/testing/sqllogictest.d.ts +89 -0
- package/dist/testing/sqllogictest.js +434 -0
- package/dist/transactions/index.d.ts +92 -16
- package/dist/transactions/index.js +1074 -236
- package/dist/worker-protocol/index.d.ts +3 -2
- package/dist/worker-protocol/index.js +2 -5
- package/package.json +53 -3
- package/postgres-feature-profile.json +223 -0
- package/sql-feature-matrix.json +172 -252
- 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-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/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/sql-json.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { stringArgument } from "./sql-semantics.js";
|
|
2
|
+
import { externalSqlDomainValue } from "./sql-domains.js";
|
|
3
|
+
import { MAX_CACHEABLE_TEXT_CHARACTERS, MAX_SQL_SCALAR_RESULT_CHARACTERS } from "./cache-limits.js";
|
|
2
4
|
export function parseJsonPath(path, caller) {
|
|
3
5
|
const text = stringArgument(caller, path).trim();
|
|
6
|
+
if (text.length > MAX_CACHEABLE_TEXT_CHARACTERS) {
|
|
7
|
+
throw new RangeError(`${caller} path exceeds ${String(MAX_CACHEABLE_TEXT_CHARACTERS)} characters`);
|
|
8
|
+
}
|
|
4
9
|
if (!text.startsWith("$"))
|
|
5
10
|
throw new TypeError(`${caller} paths start at $`);
|
|
6
11
|
const steps = [];
|
|
@@ -41,8 +46,9 @@ export function parseJsonPath(path, caller) {
|
|
|
41
46
|
export function jsonAtPath(document, path, caller) {
|
|
42
47
|
const steps = parseJsonPath(path, caller);
|
|
43
48
|
let current;
|
|
49
|
+
const source = boundedJsonDocument(document, caller);
|
|
44
50
|
try {
|
|
45
|
-
current = JSON.parse(
|
|
51
|
+
current = JSON.parse(source);
|
|
46
52
|
}
|
|
47
53
|
catch {
|
|
48
54
|
// A document that is not JSON selects nothing rather than failing the whole statement,
|
|
@@ -71,8 +77,12 @@ export function jsonAtPath(document, path, caller) {
|
|
|
71
77
|
}
|
|
72
78
|
/** Whether a value is JSON text of the requested shape (T825). */
|
|
73
79
|
export function jsonIsValid(document, kind) {
|
|
80
|
+
document = externalSqlDomainValue(document);
|
|
74
81
|
if (typeof document !== "string")
|
|
75
82
|
return false;
|
|
83
|
+
if (document.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
84
|
+
throw new RangeError(`JSON document exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
85
|
+
}
|
|
76
86
|
let parsed;
|
|
77
87
|
try {
|
|
78
88
|
parsed = JSON.parse(document);
|
|
@@ -94,27 +104,77 @@ export function jsonIsValid(document, kind) {
|
|
|
94
104
|
}
|
|
95
105
|
/** A SQL value as its JSON counterpart: datetimes serialize as ISO text, like every cast. */
|
|
96
106
|
export function jsonValueOf(value) {
|
|
97
|
-
|
|
107
|
+
const external = externalSqlDomainValue(value);
|
|
108
|
+
return external instanceof Date ? dateIsoString(external) : external;
|
|
98
109
|
}
|
|
99
110
|
/**
|
|
100
|
-
* JSON_ARRAY(v, ...) and JSON_OBJECT(k, v, ...) (T811/T812).
|
|
101
|
-
*
|
|
102
|
-
*
|
|
111
|
+
* JSON_ARRAY(v, ...) and JSON_OBJECT(k, v, ...) (T811/T812). The omitted null-handling clause is
|
|
112
|
+
* `NULL ON NULL`: SQL NULL becomes a JSON null. `ABSENT ON NULL` is a separate spelling rather
|
|
113
|
+
* than the default. Constructors return JSON text at the JavaScript boundary.
|
|
103
114
|
*/
|
|
104
115
|
export function jsonConstructor(name, values) {
|
|
105
116
|
if (name === "JSON_ARRAY") {
|
|
106
|
-
|
|
117
|
+
const members = values.map((value) => boundedJsonValue(value ?? null, "JSON_ARRAY value"));
|
|
118
|
+
return joinBoundedJson("[", members, "]", "JSON_ARRAY result");
|
|
107
119
|
}
|
|
108
|
-
const
|
|
120
|
+
const members = [];
|
|
109
121
|
for (let index = 0; index + 1 < values.length; index += 2) {
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
throw new TypeError("JSON_OBJECT keys
|
|
122
|
+
const rawKey = values[index];
|
|
123
|
+
if (rawKey === null || rawKey === undefined) {
|
|
124
|
+
throw new TypeError("JSON_OBJECT keys cannot be NULL");
|
|
125
|
+
}
|
|
126
|
+
let key;
|
|
127
|
+
if (rawKey instanceof Date)
|
|
128
|
+
key = dateIsoString(rawKey);
|
|
129
|
+
else if (typeof rawKey === "string")
|
|
130
|
+
key = rawKey;
|
|
131
|
+
else if (typeof rawKey === "number" || typeof rawKey === "boolean")
|
|
132
|
+
key = String(rawKey);
|
|
133
|
+
else
|
|
134
|
+
throw new TypeError("JSON_OBJECT keys must be scalar values");
|
|
113
135
|
const member = values[index + 1];
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
136
|
+
// Build JSON text directly. WITHOUT UNIQUE KEYS is the default, so duplicate names must be
|
|
137
|
+
// preserved; assigning through a JavaScript object would collapse them and mishandle
|
|
138
|
+
// special names such as "__proto__".
|
|
139
|
+
const encodedKey = boundedJsonValue(key, "JSON_OBJECT key");
|
|
140
|
+
const encodedValue = boundedJsonValue(member ?? null, "JSON_OBJECT value");
|
|
141
|
+
members.push(`${encodedKey}:${encodedValue}`);
|
|
142
|
+
}
|
|
143
|
+
return joinBoundedJson("{", members, "}", "JSON_OBJECT result");
|
|
144
|
+
}
|
|
145
|
+
function boundedJsonDocument(value, caller) {
|
|
146
|
+
const document = stringArgument(caller, externalSqlDomainValue(value));
|
|
147
|
+
if (document.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
148
|
+
throw new RangeError(`${caller} document exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
149
|
+
}
|
|
150
|
+
return document;
|
|
151
|
+
}
|
|
152
|
+
function boundedJsonValue(value, label) {
|
|
153
|
+
const normalized = jsonValueOf(value);
|
|
154
|
+
if (typeof normalized === "string" && normalized.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
155
|
+
throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
156
|
+
}
|
|
157
|
+
const encoded = JSON.stringify(normalized);
|
|
158
|
+
if (typeof encoded !== "string")
|
|
159
|
+
throw new TypeError(`${label} is not JSON serializable`);
|
|
160
|
+
if (encoded.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
161
|
+
throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
162
|
+
}
|
|
163
|
+
return encoded;
|
|
164
|
+
}
|
|
165
|
+
function joinBoundedJson(prefix, members, suffix, label) {
|
|
166
|
+
let length = prefix.length + suffix.length;
|
|
167
|
+
for (const member of members) {
|
|
168
|
+
length += member.length;
|
|
169
|
+
if (!Number.isSafeInteger(length) || length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
170
|
+
throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (members.length > 1)
|
|
174
|
+
length += members.length - 1;
|
|
175
|
+
if (!Number.isSafeInteger(length) || length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
176
|
+
throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
117
177
|
}
|
|
118
|
-
return
|
|
178
|
+
return `${prefix}${members.join(",")}${suffix}`;
|
|
119
179
|
}
|
|
120
|
-
|
|
180
|
+
import { dateIsoString } from "../date-value.js";
|
|
@@ -18,8 +18,14 @@ export declare function encodeSqlEqualityValue(value: unknown): readonly unknown
|
|
|
18
18
|
* integer and clamps to 0..30, with ties rounded away from zero.
|
|
19
19
|
*/
|
|
20
20
|
export declare function roundSqlNumber(value: number, precision?: number): number;
|
|
21
|
-
/**
|
|
22
|
-
export
|
|
21
|
+
/** A whole-string SQL pattern matcher. Unlike RegExp, test never coerces its input. */
|
|
22
|
+
export interface SqlPatternMatcher {
|
|
23
|
+
test(value: string): boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Compiles SQL LIKE without exposing input to the host regular-expression engine. */
|
|
26
|
+
export declare function compileLikePattern(pattern: string, caseInsensitive?: boolean, escape?: string): SqlPatternMatcher;
|
|
27
|
+
/** PostgreSQL SIMILAR TO compiled to a Thompson NFA with bounded deterministic work. */
|
|
28
|
+
export declare function compileSimilarPattern(pattern: string, escape?: string): SqlPatternMatcher;
|
|
23
29
|
/** Defines an own enumerable result-column property, including the special name `__proto__`. */
|
|
24
30
|
export declare function defineSqlResultProperty(target: Record<string, unknown>, name: string, value: unknown): void;
|
|
25
31
|
/**
|
|
@@ -28,4 +34,3 @@ export declare function defineSqlResultProperty(target: Record<string, unknown>,
|
|
|
28
34
|
* a silent coercion here would make `LENGTH(42)` answer instead of failing.
|
|
29
35
|
*/
|
|
30
36
|
export declare function stringArgument(name: string, value: unknown): string;
|
|
31
|
-
//# sourceMappingURL=sql-semantics.d.ts.map
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { dateMilliseconds } from "../date-value.js";
|
|
2
|
+
import { assertWellFormedString } from "../block-format/unicode.js";
|
|
3
|
+
import { MAX_SQL_NESTING_DEPTH, MAX_SQL_PATTERN_CHARACTERS, MAX_SQL_PATTERN_MATCH_STEPS, } from "./cache-limits.js";
|
|
4
|
+
import { collatedDomainCompare, enumDomainCompare, exactNumericCompare, externalSqlDomainValue, externalSqlTextValue, } from "./sql-domains.js";
|
|
1
5
|
/**
|
|
2
6
|
* SQL value semantics shared by the row and vector executors.
|
|
3
7
|
*
|
|
@@ -9,8 +13,26 @@
|
|
|
9
13
|
* and signed zero compares equal because SQL numeric equality does not distinguish it.
|
|
10
14
|
*/
|
|
11
15
|
export function compareSqlValues(left, right) {
|
|
12
|
-
const
|
|
13
|
-
const
|
|
16
|
+
const plainLeft = externalSqlTextValue(left);
|
|
17
|
+
const plainRight = externalSqlTextValue(right);
|
|
18
|
+
if ((typeof left === "string" && plainLeft !== left) ||
|
|
19
|
+
(typeof right === "string" && plainRight !== right)) {
|
|
20
|
+
if (typeof plainLeft !== "string" || typeof plainRight !== "string") {
|
|
21
|
+
throw new TypeError("Values must have comparable SQL types");
|
|
22
|
+
}
|
|
23
|
+
return compareSqlStrings(plainLeft, plainRight);
|
|
24
|
+
}
|
|
25
|
+
const collated = collatedDomainCompare(left, right);
|
|
26
|
+
if (collated !== undefined)
|
|
27
|
+
return collated;
|
|
28
|
+
const enumOrder = enumDomainCompare(left, right);
|
|
29
|
+
if (enumOrder !== undefined)
|
|
30
|
+
return enumOrder;
|
|
31
|
+
const exact = exactNumericCompare(left, right);
|
|
32
|
+
if (exact !== undefined)
|
|
33
|
+
return exact;
|
|
34
|
+
const a = left instanceof Date ? dateMilliseconds(left) : left;
|
|
35
|
+
const b = right instanceof Date ? dateMilliseconds(right) : right;
|
|
14
36
|
if (a === b)
|
|
15
37
|
return 0;
|
|
16
38
|
if (a === null || a === undefined)
|
|
@@ -45,9 +67,9 @@ export function encodeSqlEqualityValue(value) {
|
|
|
45
67
|
if (typeof value === "number")
|
|
46
68
|
return [2, String(value === 0 ? 0 : value)];
|
|
47
69
|
if (typeof value === "string")
|
|
48
|
-
return [3, value];
|
|
70
|
+
return [3, externalSqlTextValue(value)];
|
|
49
71
|
if (value instanceof Date)
|
|
50
|
-
return [4, value
|
|
72
|
+
return [4, dateMilliseconds(value)];
|
|
51
73
|
throw new TypeError("Query produced an unsupported value");
|
|
52
74
|
}
|
|
53
75
|
/**
|
|
@@ -61,43 +83,449 @@ export function roundSqlNumber(value, precision = 0) {
|
|
|
61
83
|
const rounded = Number(value.toFixed(digits));
|
|
62
84
|
return rounded === 0 ? 0 : rounded;
|
|
63
85
|
}
|
|
86
|
+
const MAX_PATTERN_CACHE_ENTRIES = 128;
|
|
87
|
+
const MAX_PATTERN_CACHE_RETAINED_SIZE = 64 * 1024;
|
|
64
88
|
const likeCache = new Map();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
const similarCache = new Map();
|
|
90
|
+
let likeCacheRetainedSize = 0;
|
|
91
|
+
let similarCacheRetainedSize = 0;
|
|
92
|
+
function cachedPattern(cache, key) {
|
|
93
|
+
const entry = cache.get(key);
|
|
94
|
+
if (entry === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
cache.delete(key);
|
|
97
|
+
cache.set(key, entry);
|
|
98
|
+
return entry[0];
|
|
99
|
+
}
|
|
100
|
+
function retainPattern(cache, key, matcher, retainedSize, setRetainedSize) {
|
|
101
|
+
const entrySize = key.length + matcher.retainedSize;
|
|
102
|
+
if (entrySize > MAX_PATTERN_CACHE_RETAINED_SIZE)
|
|
103
|
+
return;
|
|
104
|
+
while (cache.size >= MAX_PATTERN_CACHE_ENTRIES ||
|
|
105
|
+
retainedSize + entrySize > MAX_PATTERN_CACHE_RETAINED_SIZE) {
|
|
106
|
+
const oldest = cache.keys().next().value;
|
|
107
|
+
if (oldest === undefined)
|
|
108
|
+
break;
|
|
109
|
+
const removed = cache.get(oldest);
|
|
110
|
+
cache.delete(oldest);
|
|
111
|
+
retainedSize -= removed?.[1] ?? 0;
|
|
112
|
+
}
|
|
113
|
+
cache.set(key, [matcher, entrySize]);
|
|
114
|
+
setRetainedSize(retainedSize + entrySize);
|
|
115
|
+
}
|
|
116
|
+
function throwPatternWorkLimit() {
|
|
117
|
+
throw new RangeError(`SQL pattern match exceeds ${String(MAX_SQL_PATTERN_MATCH_STEPS)} deterministic steps`);
|
|
118
|
+
}
|
|
119
|
+
function exactSingleCharacter(value, label) {
|
|
120
|
+
assertBoundedPattern(value, label, 2);
|
|
121
|
+
const characters = value[Symbol.iterator]();
|
|
122
|
+
const first = characters.next();
|
|
123
|
+
if (first.done === true || characters.next().done !== true) {
|
|
124
|
+
throw new TypeError(`${label.replace(" escape", " ESCAPE")} takes one character`);
|
|
125
|
+
}
|
|
126
|
+
return first.value;
|
|
127
|
+
}
|
|
128
|
+
function tokenizeLike(pattern, escape) {
|
|
129
|
+
const tokens = [];
|
|
72
130
|
let escaped = false;
|
|
73
131
|
for (const character of pattern) {
|
|
74
132
|
if (escaped) {
|
|
75
|
-
|
|
133
|
+
tokens.push(character);
|
|
76
134
|
escaped = false;
|
|
77
|
-
continue;
|
|
78
135
|
}
|
|
79
|
-
if (escape !== undefined && character === escape) {
|
|
136
|
+
else if (escape !== undefined && character === escape) {
|
|
80
137
|
escaped = true;
|
|
81
|
-
continue;
|
|
82
138
|
}
|
|
83
|
-
if (character === "%")
|
|
84
|
-
|
|
139
|
+
else if (character === "%") {
|
|
140
|
+
if (tokens.at(-1) !== 0)
|
|
141
|
+
tokens.push(0);
|
|
142
|
+
}
|
|
85
143
|
else if (character === "_")
|
|
86
|
-
|
|
144
|
+
tokens.push(1);
|
|
87
145
|
else
|
|
88
|
-
|
|
146
|
+
tokens.push(character);
|
|
89
147
|
}
|
|
90
148
|
if (escaped)
|
|
91
149
|
throw new TypeError("LIKE pattern ends with a dangling escape character");
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
150
|
+
return tokens;
|
|
151
|
+
}
|
|
152
|
+
/** Unicode simple-case approximation used consistently by row and vector LIKE execution. */
|
|
153
|
+
function simpleCaseFold(character) {
|
|
154
|
+
const upper = character.toUpperCase();
|
|
155
|
+
const codePoints = upper[Symbol.iterator]();
|
|
156
|
+
return codePoints.next().done !== true && codePoints.next().done === true ? upper : character;
|
|
157
|
+
}
|
|
158
|
+
function likeCharactersEqual(left, right, caseInsensitive) {
|
|
159
|
+
return left === right || (caseInsensitive && simpleCaseFold(left) === simpleCaseFold(right));
|
|
160
|
+
}
|
|
161
|
+
function literalLikeMatcher(tokens, caseInsensitive) {
|
|
162
|
+
if (tokens.includes(1))
|
|
163
|
+
return undefined;
|
|
164
|
+
const leading = tokens[0] === 0;
|
|
165
|
+
const trailing = tokens.at(-1) === 0;
|
|
166
|
+
const begin = leading ? 1 : 0;
|
|
167
|
+
const end = trailing ? tokens.length - 1 : tokens.length;
|
|
168
|
+
if (tokens.slice(begin, end).some((token) => typeof token !== "string"))
|
|
169
|
+
return undefined;
|
|
170
|
+
const body = tokens
|
|
171
|
+
.slice(begin, end)
|
|
172
|
+
.map((token) => (typeof token === "string" ? token : ""))
|
|
173
|
+
.join("");
|
|
174
|
+
if (!caseInsensitive) {
|
|
175
|
+
const test = (value) => {
|
|
176
|
+
if (typeof value !== "string")
|
|
177
|
+
throw new TypeError("LIKE input must be a string");
|
|
178
|
+
if (leading && trailing)
|
|
179
|
+
return value.includes(body);
|
|
180
|
+
if (leading)
|
|
181
|
+
return value.endsWith(body);
|
|
182
|
+
if (trailing)
|
|
183
|
+
return value.startsWith(body);
|
|
184
|
+
return value === body;
|
|
185
|
+
};
|
|
186
|
+
return { test, retainedSize: tokens.length * 16 + body.length * 2 };
|
|
187
|
+
}
|
|
188
|
+
const needle = Array.from(body, simpleCaseFold);
|
|
189
|
+
const prefix = new Uint32Array(needle.length);
|
|
190
|
+
for (let index = 1, matched = 0; index < needle.length; index += 1) {
|
|
191
|
+
while (matched > 0 && needle[index] !== needle[matched])
|
|
192
|
+
matched = prefix[matched - 1] ?? 0;
|
|
193
|
+
if (needle[index] === needle[matched])
|
|
194
|
+
matched += 1;
|
|
195
|
+
prefix[index] = matched;
|
|
196
|
+
}
|
|
197
|
+
const test = (value) => {
|
|
198
|
+
if (typeof value !== "string")
|
|
199
|
+
throw new TypeError("LIKE input must be a string");
|
|
200
|
+
if (needle.length === 0)
|
|
201
|
+
return leading || trailing || value.length === 0;
|
|
202
|
+
let matched = 0;
|
|
203
|
+
let position = 0;
|
|
204
|
+
let matchedAtEnd = false;
|
|
205
|
+
for (const rawCharacter of value) {
|
|
206
|
+
matchedAtEnd = false;
|
|
207
|
+
const character = simpleCaseFold(rawCharacter);
|
|
208
|
+
while (matched > 0 && character !== needle[matched])
|
|
209
|
+
matched = prefix[matched - 1] ?? 0;
|
|
210
|
+
if (character === needle[matched])
|
|
211
|
+
matched += 1;
|
|
212
|
+
position += 1;
|
|
213
|
+
if (matched === needle.length) {
|
|
214
|
+
const startsAt = position - needle.length;
|
|
215
|
+
if (leading && trailing)
|
|
216
|
+
return true;
|
|
217
|
+
if (!leading && startsAt === 0) {
|
|
218
|
+
if (trailing)
|
|
219
|
+
return true;
|
|
220
|
+
matchedAtEnd = true;
|
|
221
|
+
}
|
|
222
|
+
else if (leading && !trailing)
|
|
223
|
+
matchedAtEnd = true;
|
|
224
|
+
matched = prefix[matched - 1] ?? 0;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return matchedAtEnd;
|
|
228
|
+
};
|
|
229
|
+
return {
|
|
230
|
+
test,
|
|
231
|
+
retainedSize: tokens.length * 16 + body.length * 2 + prefix.byteLength,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function generalLikeMatcher(tokens, caseInsensitive) {
|
|
235
|
+
const test = (value) => {
|
|
236
|
+
if (typeof value !== "string")
|
|
237
|
+
throw new TypeError("LIKE input must be a string");
|
|
238
|
+
let remaining = MAX_SQL_PATTERN_MATCH_STEPS;
|
|
239
|
+
let tokenIndex = 0;
|
|
240
|
+
let valueIndex = 0;
|
|
241
|
+
let manyIndex = -1;
|
|
242
|
+
let retryValueIndex = 0;
|
|
243
|
+
while (valueIndex < value.length) {
|
|
244
|
+
if (--remaining < 0)
|
|
245
|
+
throwPatternWorkLimit();
|
|
246
|
+
const token = tokens[tokenIndex];
|
|
247
|
+
if (token === 0) {
|
|
248
|
+
manyIndex = tokenIndex;
|
|
249
|
+
tokenIndex += 1;
|
|
250
|
+
retryValueIndex = valueIndex;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const codePoint = value.codePointAt(valueIndex);
|
|
254
|
+
if (codePoint === undefined)
|
|
255
|
+
break;
|
|
256
|
+
const character = String.fromCodePoint(codePoint);
|
|
257
|
+
if (token === 1 ||
|
|
258
|
+
(typeof token === "string" && likeCharactersEqual(token, character, caseInsensitive))) {
|
|
259
|
+
tokenIndex += 1;
|
|
260
|
+
valueIndex += character.length;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (manyIndex < 0)
|
|
264
|
+
return false;
|
|
265
|
+
const retryCodePoint = value.codePointAt(retryValueIndex);
|
|
266
|
+
if (retryCodePoint === undefined)
|
|
267
|
+
return false;
|
|
268
|
+
retryValueIndex += String.fromCodePoint(retryCodePoint).length;
|
|
269
|
+
valueIndex = retryValueIndex;
|
|
270
|
+
tokenIndex = manyIndex + 1;
|
|
271
|
+
}
|
|
272
|
+
while (tokens[tokenIndex] === 0) {
|
|
273
|
+
if (--remaining < 0)
|
|
274
|
+
throwPatternWorkLimit();
|
|
275
|
+
tokenIndex += 1;
|
|
276
|
+
}
|
|
277
|
+
return tokenIndex === tokens.length;
|
|
278
|
+
};
|
|
279
|
+
return { test, retainedSize: tokens.length * 16 };
|
|
280
|
+
}
|
|
281
|
+
/** Compiles SQL LIKE without exposing input to the host regular-expression engine. */
|
|
282
|
+
export function compileLikePattern(pattern, caseInsensitive = false, escape) {
|
|
283
|
+
assertBoundedPattern(pattern, "LIKE pattern");
|
|
284
|
+
const exactEscape = escape === undefined ? undefined : exactSingleCharacter(escape, "LIKE escape");
|
|
285
|
+
const key = JSON.stringify([caseInsensitive, exactEscape ?? null, pattern]);
|
|
286
|
+
const cached = cachedPattern(likeCache, key);
|
|
287
|
+
if (cached !== undefined)
|
|
288
|
+
return cached;
|
|
289
|
+
const tokens = tokenizeLike(pattern, exactEscape);
|
|
290
|
+
const matcher = literalLikeMatcher(tokens, caseInsensitive) ?? generalLikeMatcher(tokens, caseInsensitive);
|
|
291
|
+
retainPattern(likeCache, key, matcher, likeCacheRetainedSize, (size) => {
|
|
292
|
+
likeCacheRetainedSize = size;
|
|
293
|
+
});
|
|
294
|
+
return matcher;
|
|
295
|
+
}
|
|
296
|
+
const ANY_CHARACTER = () => true;
|
|
297
|
+
/** Parses SIMILAR TO directly into an NFA, avoiding an allocation-heavy intermediate AST. */
|
|
298
|
+
class SimilarCompiler {
|
|
299
|
+
escape;
|
|
300
|
+
#characters;
|
|
301
|
+
states = [];
|
|
302
|
+
#index = 0;
|
|
303
|
+
constructor(pattern, escape) {
|
|
304
|
+
this.escape = escape;
|
|
305
|
+
this.#characters = Array.from(pattern);
|
|
306
|
+
}
|
|
307
|
+
compile() {
|
|
308
|
+
const fragment = this.#alternative(0);
|
|
309
|
+
if (this.#index !== this.#characters.length) {
|
|
310
|
+
throw new TypeError("SIMILAR TO pattern has an unmatched closing parenthesis");
|
|
311
|
+
}
|
|
312
|
+
return fragment;
|
|
313
|
+
}
|
|
314
|
+
#state() {
|
|
315
|
+
this.states.push([[]]);
|
|
316
|
+
return this.states.length - 1;
|
|
317
|
+
}
|
|
318
|
+
#empty() {
|
|
319
|
+
const start = this.#state();
|
|
320
|
+
const end = this.#state();
|
|
321
|
+
this.states[start]?.[0].push(end);
|
|
322
|
+
return [start, end];
|
|
323
|
+
}
|
|
324
|
+
#consume(matches) {
|
|
325
|
+
const start = this.#state();
|
|
326
|
+
const end = this.#state();
|
|
327
|
+
const state = this.states[start];
|
|
328
|
+
if (state !== undefined)
|
|
329
|
+
state[1] = [end, matches];
|
|
330
|
+
return [start, end];
|
|
331
|
+
}
|
|
332
|
+
#repeat(fragment, plus) {
|
|
333
|
+
const start = this.#state();
|
|
334
|
+
const end = this.#state();
|
|
335
|
+
this.states[start]?.[0].push(fragment[0]);
|
|
336
|
+
if (!plus)
|
|
337
|
+
this.states[start]?.[0].push(end);
|
|
338
|
+
this.states[fragment[1]]?.[0].push(fragment[0], end);
|
|
339
|
+
return [start, end];
|
|
340
|
+
}
|
|
341
|
+
#alternative(depth) {
|
|
342
|
+
const first = this.#sequence(depth);
|
|
343
|
+
if (this.#characters[this.#index] !== "|" || this.#characters[this.#index] === this.escape) {
|
|
344
|
+
return first;
|
|
345
|
+
}
|
|
346
|
+
const start = this.#state();
|
|
347
|
+
const end = this.#state();
|
|
348
|
+
this.states[start]?.[0].push(first[0]);
|
|
349
|
+
this.states[first[1]]?.[0].push(end);
|
|
350
|
+
while (this.#characters[this.#index] === "|" && this.#characters[this.#index] !== this.escape) {
|
|
351
|
+
this.#index += 1;
|
|
352
|
+
const next = this.#sequence(depth);
|
|
353
|
+
this.states[start]?.[0].push(next[0]);
|
|
354
|
+
this.states[next[1]]?.[0].push(end);
|
|
355
|
+
}
|
|
356
|
+
return [start, end];
|
|
357
|
+
}
|
|
358
|
+
#sequence(depth) {
|
|
359
|
+
let result;
|
|
360
|
+
for (;;) {
|
|
361
|
+
const character = this.#characters[this.#index];
|
|
362
|
+
if (character === undefined ||
|
|
363
|
+
((character === "|" || character === ")") && character !== this.escape)) {
|
|
364
|
+
return result ?? this.#empty();
|
|
365
|
+
}
|
|
366
|
+
let fragment = this.#atom(depth);
|
|
367
|
+
const quantifier = this.#characters[this.#index];
|
|
368
|
+
if ((quantifier === "*" || quantifier === "+") && quantifier !== this.escape) {
|
|
369
|
+
this.#index += 1;
|
|
370
|
+
fragment = this.#repeat(fragment, quantifier === "+");
|
|
371
|
+
const repeated = this.#characters[this.#index];
|
|
372
|
+
if ((repeated === "*" || repeated === "+") && repeated !== this.escape) {
|
|
373
|
+
throw new TypeError("SIMILAR TO pattern repeats a quantifier");
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (result === undefined)
|
|
377
|
+
result = fragment;
|
|
378
|
+
else {
|
|
379
|
+
this.states[result[1]]?.[0].push(fragment[0]);
|
|
380
|
+
result = [result[0], fragment[1]];
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
#atom(depth) {
|
|
385
|
+
const character = this.#characters[this.#index];
|
|
386
|
+
if (character === undefined)
|
|
387
|
+
return this.#empty();
|
|
388
|
+
this.#index += 1;
|
|
389
|
+
if (character === this.escape) {
|
|
390
|
+
const literal = this.#characters[this.#index];
|
|
391
|
+
if (literal === undefined) {
|
|
392
|
+
throw new TypeError("SIMILAR TO pattern ends with its escape character");
|
|
393
|
+
}
|
|
394
|
+
this.#index += 1;
|
|
395
|
+
return this.#consume((candidate) => candidate === literal);
|
|
396
|
+
}
|
|
397
|
+
if (character === "*" || character === "+") {
|
|
398
|
+
throw new TypeError("SIMILAR TO quantifier has no preceding expression");
|
|
399
|
+
}
|
|
400
|
+
if (character === "(") {
|
|
401
|
+
if (depth >= MAX_SQL_NESTING_DEPTH) {
|
|
402
|
+
throw new RangeError(`SIMILAR TO nesting exceeds ${String(MAX_SQL_NESTING_DEPTH)} levels`);
|
|
403
|
+
}
|
|
404
|
+
const child = this.#alternative(depth + 1);
|
|
405
|
+
if (this.#characters[this.#index] !== ")") {
|
|
406
|
+
throw new TypeError("SIMILAR TO pattern has an unmatched opening parenthesis");
|
|
407
|
+
}
|
|
408
|
+
this.#index += 1;
|
|
409
|
+
return child;
|
|
410
|
+
}
|
|
411
|
+
if (character === "[")
|
|
412
|
+
return this.#consume(this.#characterClass());
|
|
413
|
+
if (character === "%")
|
|
414
|
+
return this.#repeat(this.#consume(ANY_CHARACTER), false);
|
|
415
|
+
if (character === "_")
|
|
416
|
+
return this.#consume(ANY_CHARACTER);
|
|
417
|
+
return this.#consume((candidate) => candidate === character);
|
|
418
|
+
}
|
|
419
|
+
#characterClass() {
|
|
420
|
+
let source = "[";
|
|
421
|
+
let closed = false;
|
|
422
|
+
let hasMember = false;
|
|
423
|
+
while (this.#index < this.#characters.length) {
|
|
424
|
+
const character = this.#characters[this.#index] ?? "";
|
|
425
|
+
this.#index += 1;
|
|
426
|
+
if (character === this.escape) {
|
|
427
|
+
const literal = this.#characters[this.#index];
|
|
428
|
+
if (literal === undefined) {
|
|
429
|
+
throw new TypeError("SIMILAR TO pattern ends with its escape character");
|
|
430
|
+
}
|
|
431
|
+
this.#index += 1;
|
|
432
|
+
source += ["\\", "]", "^", "-"].includes(literal) ? `\\${literal}` : literal;
|
|
433
|
+
hasMember = true;
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (character === "]") {
|
|
437
|
+
closed = true;
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
source += character;
|
|
441
|
+
if (character !== "^" || source.length > 2)
|
|
442
|
+
hasMember = true;
|
|
443
|
+
}
|
|
444
|
+
if (!closed)
|
|
445
|
+
throw new TypeError("SIMILAR TO pattern has an unterminated character class");
|
|
446
|
+
if (!hasMember)
|
|
447
|
+
throw new TypeError("SIMILAR TO pattern has an empty character class");
|
|
448
|
+
source += "]";
|
|
449
|
+
let expression;
|
|
450
|
+
try {
|
|
451
|
+
// The host expression is deliberately limited to one character class and one codepoint.
|
|
452
|
+
// It cannot contain repetition, grouping, or alternation, so backtracking work is constant.
|
|
453
|
+
expression = new RegExp(`^(?:${source})$`, "u");
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
throw new TypeError("SIMILAR TO pattern has an invalid character class");
|
|
457
|
+
}
|
|
458
|
+
return (candidate) => expression.test(candidate);
|
|
459
|
+
}
|
|
98
460
|
}
|
|
99
|
-
function
|
|
100
|
-
|
|
461
|
+
function nfaMatcher(pattern, escape) {
|
|
462
|
+
const compiler = new SimilarCompiler(pattern, escape);
|
|
463
|
+
const fragment = compiler.compile();
|
|
464
|
+
const { states } = compiler;
|
|
465
|
+
const test = (value) => {
|
|
466
|
+
if (typeof value !== "string")
|
|
467
|
+
throw new TypeError("SIMILAR TO input must be a string");
|
|
468
|
+
let remaining = MAX_SQL_PATTERN_MATCH_STEPS;
|
|
469
|
+
const marks = new Uint32Array(states.length);
|
|
470
|
+
const stack = [];
|
|
471
|
+
let active = [];
|
|
472
|
+
let next = [];
|
|
473
|
+
let generation = 1;
|
|
474
|
+
const addClosure = (seed, output) => {
|
|
475
|
+
stack.push(seed);
|
|
476
|
+
while (stack.length > 0) {
|
|
477
|
+
if (--remaining < 0)
|
|
478
|
+
throwPatternWorkLimit();
|
|
479
|
+
const stateIndex = stack.pop();
|
|
480
|
+
if (stateIndex === undefined || marks[stateIndex] === generation)
|
|
481
|
+
continue;
|
|
482
|
+
marks[stateIndex] = generation;
|
|
483
|
+
output.push(stateIndex);
|
|
484
|
+
for (const target of states[stateIndex]?.[0] ?? [])
|
|
485
|
+
stack.push(target);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
addClosure(fragment[0], active);
|
|
489
|
+
for (const character of value) {
|
|
490
|
+
generation += 1;
|
|
491
|
+
next.length = 0;
|
|
492
|
+
for (const stateIndex of active) {
|
|
493
|
+
if (--remaining < 0)
|
|
494
|
+
throwPatternWorkLimit();
|
|
495
|
+
const transition = states[stateIndex]?.[1];
|
|
496
|
+
if (transition?.[1](character) === true)
|
|
497
|
+
addClosure(transition[0], next);
|
|
498
|
+
}
|
|
499
|
+
if (next.length === 0)
|
|
500
|
+
return false;
|
|
501
|
+
[active, next] = [next, active];
|
|
502
|
+
}
|
|
503
|
+
return active.includes(fragment[1]);
|
|
504
|
+
};
|
|
505
|
+
// State objects, epsilon arrays, transition objects, and matcher closures all cost materially
|
|
506
|
+
// more than their integer payload. A conservative model keeps large compiled patterns out of
|
|
507
|
+
// the cache instead of pretending their Uint32-sized indexes are the whole retained graph.
|
|
508
|
+
return { test, retainedSize: states.length * 32 };
|
|
509
|
+
}
|
|
510
|
+
/** PostgreSQL SIMILAR TO compiled to a Thompson NFA with bounded deterministic work. */
|
|
511
|
+
export function compileSimilarPattern(pattern, escape = "\\") {
|
|
512
|
+
assertBoundedPattern(pattern, "SIMILAR TO pattern");
|
|
513
|
+
const exactEscape = exactSingleCharacter(escape, "SIMILAR TO escape");
|
|
514
|
+
const key = JSON.stringify([exactEscape, pattern]);
|
|
515
|
+
const cached = cachedPattern(similarCache, key);
|
|
516
|
+
if (cached !== undefined)
|
|
517
|
+
return cached;
|
|
518
|
+
const matcher = nfaMatcher(pattern, exactEscape);
|
|
519
|
+
retainPattern(similarCache, key, matcher, similarCacheRetainedSize, (size) => {
|
|
520
|
+
similarCacheRetainedSize = size;
|
|
521
|
+
});
|
|
522
|
+
return matcher;
|
|
523
|
+
}
|
|
524
|
+
function assertBoundedPattern(value, label, limit = MAX_SQL_PATTERN_CHARACTERS) {
|
|
525
|
+
if (value.length > limit) {
|
|
526
|
+
throw new RangeError(`${label} exceeds ${String(limit)} characters`);
|
|
527
|
+
}
|
|
528
|
+
assertWellFormedString(value, label);
|
|
101
529
|
}
|
|
102
530
|
/** Defines an own enumerable result-column property, including the special name `__proto__`. */
|
|
103
531
|
export function defineSqlResultProperty(target, name, value) {
|
|
@@ -114,8 +542,8 @@ export function defineSqlResultProperty(target, name, value) {
|
|
|
114
542
|
* a silent coercion here would make `LENGTH(42)` answer instead of failing.
|
|
115
543
|
*/
|
|
116
544
|
export function stringArgument(name, value) {
|
|
117
|
-
|
|
545
|
+
const external = externalSqlDomainValue(value);
|
|
546
|
+
if (typeof external !== "string")
|
|
118
547
|
throw new TypeError(`${name} requires a string argument`);
|
|
119
|
-
return
|
|
548
|
+
return external;
|
|
120
549
|
}
|
|
121
|
-
//# sourceMappingURL=sql-semantics.js.map
|