@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
|
@@ -1,3 +1,6 @@
|
|
|
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";
|
|
1
4
|
import { collatedDomainCompare, enumDomainCompare, exactNumericCompare, externalSqlDomainValue, externalSqlTextValue, } from "./sql-domains.js";
|
|
2
5
|
/**
|
|
3
6
|
* SQL value semantics shared by the row and vector executors.
|
|
@@ -28,8 +31,8 @@ export function compareSqlValues(left, right) {
|
|
|
28
31
|
const exact = exactNumericCompare(left, right);
|
|
29
32
|
if (exact !== undefined)
|
|
30
33
|
return exact;
|
|
31
|
-
const a = left instanceof Date ? left
|
|
32
|
-
const b = right instanceof Date ? right
|
|
34
|
+
const a = left instanceof Date ? dateMilliseconds(left) : left;
|
|
35
|
+
const b = right instanceof Date ? dateMilliseconds(right) : right;
|
|
33
36
|
if (a === b)
|
|
34
37
|
return 0;
|
|
35
38
|
if (a === null || a === undefined)
|
|
@@ -66,7 +69,7 @@ export function encodeSqlEqualityValue(value) {
|
|
|
66
69
|
if (typeof value === "string")
|
|
67
70
|
return [3, externalSqlTextValue(value)];
|
|
68
71
|
if (value instanceof Date)
|
|
69
|
-
return [4, value
|
|
72
|
+
return [4, dateMilliseconds(value)];
|
|
70
73
|
throw new TypeError("Query produced an unsupported value");
|
|
71
74
|
}
|
|
72
75
|
/**
|
|
@@ -80,89 +83,449 @@ export function roundSqlNumber(value, precision = 0) {
|
|
|
80
83
|
const rounded = Number(value.toFixed(digits));
|
|
81
84
|
return rounded === 0 ? 0 : rounded;
|
|
82
85
|
}
|
|
86
|
+
const MAX_PATTERN_CACHE_ENTRIES = 128;
|
|
87
|
+
const MAX_PATTERN_CACHE_RETAINED_SIZE = 64 * 1024;
|
|
83
88
|
const likeCache = new Map();
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 = [];
|
|
91
130
|
let escaped = false;
|
|
92
131
|
for (const character of pattern) {
|
|
93
132
|
if (escaped) {
|
|
94
|
-
|
|
133
|
+
tokens.push(character);
|
|
95
134
|
escaped = false;
|
|
96
|
-
continue;
|
|
97
135
|
}
|
|
98
|
-
if (escape !== undefined && character === escape) {
|
|
136
|
+
else if (escape !== undefined && character === escape) {
|
|
99
137
|
escaped = true;
|
|
100
|
-
continue;
|
|
101
138
|
}
|
|
102
|
-
if (character === "%")
|
|
103
|
-
|
|
139
|
+
else if (character === "%") {
|
|
140
|
+
if (tokens.at(-1) !== 0)
|
|
141
|
+
tokens.push(0);
|
|
142
|
+
}
|
|
104
143
|
else if (character === "_")
|
|
105
|
-
|
|
144
|
+
tokens.push(1);
|
|
106
145
|
else
|
|
107
|
-
|
|
146
|
+
tokens.push(character);
|
|
108
147
|
}
|
|
109
148
|
if (escaped)
|
|
110
149
|
throw new TypeError("LIKE pattern ends with a dangling escape character");
|
|
111
|
-
|
|
112
|
-
const regExp = new RegExp(source, caseInsensitive ? "iu" : "u");
|
|
113
|
-
if (likeCache.size >= 128)
|
|
114
|
-
likeCache.clear();
|
|
115
|
-
likeCache.set(key, regExp);
|
|
116
|
-
return regExp;
|
|
150
|
+
return tokens;
|
|
117
151
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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);
|
|
125
287
|
if (cached !== undefined)
|
|
126
288
|
return cached;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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");
|
|
135
311
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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;
|
|
139
345
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
source += character.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
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];
|
|
152
357
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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);
|
|
159
418
|
}
|
|
160
|
-
|
|
161
|
-
|
|
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);
|
|
162
459
|
}
|
|
163
460
|
}
|
|
164
|
-
function
|
|
165
|
-
|
|
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);
|
|
166
529
|
}
|
|
167
530
|
/** Defines an own enumerable result-column property, including the special name `__proto__`. */
|
|
168
531
|
export function defineSqlResultProperty(target, name, value) {
|
|
@@ -184,4 +547,3 @@ export function stringArgument(name, value) {
|
|
|
184
547
|
throw new TypeError(`${name} requires a string argument`);
|
|
185
548
|
return external;
|
|
186
549
|
}
|
|
187
|
-
//# sourceMappingURL=sql-semantics.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { LiveQueryInput, LiveQueryObserveOptions } from "./live.js";
|
|
2
|
+
/** The structural live-query surface shared by MinnowDatabase and its worker client. */
|
|
3
|
+
export interface LiveQueryBackend {
|
|
4
|
+
observe(query: LiveQueryInput, options: LiveQueryObserveOptions): Promise<LiveQuerySubscriptionLike>;
|
|
5
|
+
refresh(): Promise<void>;
|
|
6
|
+
close(): void | Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export interface LiveQuerySubscriptionLike {
|
|
9
|
+
readonly dependencyTableIds: readonly string[];
|
|
10
|
+
close(): void | Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export interface LiveQueryDriver {
|
|
13
|
+
liveQueries(options?: {
|
|
14
|
+
channelName?: string;
|
|
15
|
+
pollIntervalMs?: number;
|
|
16
|
+
}): LiveQueryBackend;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Adapter-neutral query boundary. `query` drives dependency tracking in Minnow; `execute`
|
|
20
|
+
* deliberately stays with the adapter so its result transforms and inferred row type survive.
|
|
21
|
+
*/
|
|
22
|
+
export interface LiveQuerySource<out TRow> {
|
|
23
|
+
readonly query: LiveQueryInput;
|
|
24
|
+
execute(signal?: AbortSignal): Promise<readonly TRow[]>;
|
|
25
|
+
}
|
|
26
|
+
export type LiveSnapshot<TRow> = {
|
|
27
|
+
readonly status: "loading";
|
|
28
|
+
readonly rows: readonly TRow[];
|
|
29
|
+
} | {
|
|
30
|
+
readonly status: "ready";
|
|
31
|
+
readonly rows: readonly TRow[];
|
|
32
|
+
readonly version: number | null;
|
|
33
|
+
} | {
|
|
34
|
+
readonly status: "error";
|
|
35
|
+
readonly rows: readonly TRow[];
|
|
36
|
+
readonly error: unknown;
|
|
37
|
+
readonly version: number | null;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* One typed live SELECT. It is also a framework-neutral external store: getSnapshot is stable
|
|
41
|
+
* until state changes and subscribe returns synchronous cleanup. Slow consumers coalesce to the
|
|
42
|
+
* newest invalidation; errors retain the last good rows.
|
|
43
|
+
*/
|
|
44
|
+
export declare class LiveQuery<out TRow> implements AsyncIterable<LiveSnapshot<TRow>> {
|
|
45
|
+
#private;
|
|
46
|
+
/** Type-only row helper (`typeof query.$inferRow`). */
|
|
47
|
+
readonly $inferRow: TRow;
|
|
48
|
+
constructor(backend: LiveQueryBackend, source: LiveQuerySource<TRow>, onClose?: () => void);
|
|
49
|
+
getSnapshot: () => LiveSnapshot<TRow>;
|
|
50
|
+
subscribe: (listener: () => void) => (() => void);
|
|
51
|
+
refresh(): Promise<void>;
|
|
52
|
+
close(): void;
|
|
53
|
+
[Symbol.asyncIterator](): AsyncIterator<LiveSnapshot<TRow>, undefined>;
|
|
54
|
+
}
|
|
55
|
+
export interface LiveQueryManagerOptions {
|
|
56
|
+
readonly channelName?: string;
|
|
57
|
+
readonly pollIntervalMs?: number;
|
|
58
|
+
}
|
|
59
|
+
/** Owns one shared low-level set and every typed query created from it. */
|
|
60
|
+
export declare class LiveQueryManager {
|
|
61
|
+
#private;
|
|
62
|
+
constructor(driver: LiveQueryDriver, options?: LiveQueryManagerOptions);
|
|
63
|
+
watch<TRow>(source: LiveQuerySource<TRow>): LiveQuery<TRow>;
|
|
64
|
+
refresh(): Promise<void>;
|
|
65
|
+
close(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
export declare function createLiveQueryManager(driver: LiveQueryDriver, options?: LiveQueryManagerOptions): LiveQueryManager;
|