@minnowdb/core 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/block-format/block.js +254 -325
- package/dist/block-format/checksum.js +30 -52
- package/dist/block-format/codecs.js +112 -125
- package/dist/block-format/column.js +187 -205
- package/dist/block-format/index.js +5 -1
- package/dist/block-format/physical.js +371 -387
- package/dist/block-format/types.js +4 -1
- package/dist/block-format/unicode.js +30 -39
- package/dist/date-value.js +67 -78
- package/dist/engine/artifact-cache.js +106 -116
- package/dist/engine/batch.js +36 -44
- package/dist/engine/buffered-writer.js +156 -158
- package/dist/engine/byte-estimates.js +20 -22
- package/dist/engine/cache-limits.js +24 -25
- package/dist/engine/cancellation.js +5 -3
- package/dist/engine/catalog.js +64 -73
- package/dist/engine/client.js +715 -780
- package/dist/engine/database.js +15941 -19313
- package/dist/engine/defaults.js +104 -111
- package/dist/engine/errors.js +113 -128
- package/dist/engine/fts.js +233 -280
- package/dist/engine/group-index.js +287 -321
- package/dist/engine/index.js +23 -12
- package/dist/engine/join-index.js +177 -186
- package/dist/engine/keyed-live.js +168 -173
- package/dist/engine/live-api.js +0 -1
- package/dist/engine/live-equal.js +32 -37
- package/dist/engine/live.js +603 -647
- package/dist/engine/memory.js +109 -111
- package/dist/engine/optimizer.d.ts +19 -1
- package/dist/engine/optimizer.js +2633 -2934
- package/dist/engine/point-read.js +122 -182
- package/dist/engine/query-api.js +12 -3
- package/dist/engine/query-cache.js +61 -69
- package/dist/engine/query.d.ts +6 -0
- package/dist/engine/query.js +8503 -9980
- package/dist/engine/result-wire.js +221 -252
- package/dist/engine/schema-wire.js +97 -112
- package/dist/engine/schema.js +1099 -1254
- package/dist/engine/sort-keys.js +315 -389
- package/dist/engine/sql-domains.js +646 -769
- package/dist/engine/sql-driver.js +0 -1
- package/dist/engine/sql-functions.js +1031 -1125
- package/dist/engine/sql-json.js +199 -218
- package/dist/engine/sql-semantics.js +572 -639
- package/dist/engine/typed-live.js +271 -293
- package/dist/engine/vector.js +4484 -5145
- package/dist/engine/worker-host.d.ts +8 -63
- package/dist/engine/worker-host.js +28 -1130
- package/dist/engine/worker-indexeddb.d.ts +1 -0
- package/dist/engine/worker-indexeddb.js +3 -0
- package/dist/engine/worker-memory.d.ts +1 -0
- package/dist/engine/worker-memory.js +3 -0
- package/dist/engine/worker-opfs.d.ts +1 -0
- package/dist/engine/worker-opfs.js +3 -0
- package/dist/engine/worker-server.d.ts +93 -0
- package/dist/engine/worker-server.js +999 -0
- package/dist/engine/worker-store-indexeddb.d.ts +2 -0
- package/dist/engine/worker-store-indexeddb.js +11 -0
- package/dist/engine/worker-store-memory.d.ts +2 -0
- package/dist/engine/worker-store-memory.js +6 -0
- package/dist/engine/worker-store-opfs.d.ts +2 -0
- package/dist/engine/worker-store-opfs.js +9 -0
- package/dist/engine/worker.js +0 -12
- package/dist/engine/write-block-planner.js +93 -100
- package/dist/plan/index.js +15 -16
- package/dist/plan/model.d.ts +6 -0
- package/dist/plan/model.js +20 -27
- package/dist/storage/index.js +0 -12
- package/dist/storage/indexeddb.js +11776 -13171
- package/dist/storage/memory.js +1198 -1241
- package/dist/storage/opfs/files.js +238 -295
- package/dist/storage/opfs/index.js +9 -3
- package/dist/storage/opfs/leader.js +4386 -4856
- package/dist/storage/opfs/rpc.js +223 -259
- package/dist/storage/opfs/snapshot-ledger.js +219 -260
- package/dist/storage/opfs/store.js +884 -1003
- package/dist/storage/persistence.js +59 -74
- package/dist/storage/snapshot-stream.js +788 -826
- package/dist/storage/snapshot.js +36 -37
- package/dist/storage/toolkit/extents.js +444 -520
- package/dist/storage/toolkit/index.js +28 -29
- package/dist/storage/toolkit/record-core.js +5792 -6377
- package/dist/storage/toolkit/sync-file.js +34 -42
- package/dist/storage/toolkit/wal.js +92 -127
- package/dist/storage/toolkit/wire.js +232 -256
- package/dist/storage/types.js +2948 -3250
- package/dist/testing/block-store-conformance.js +1556 -1629
- package/dist/testing/index.js +310 -291
- package/dist/testing/opfs-shim.js +280 -312
- package/dist/testing/simulator.js +496 -534
- package/dist/testing/sqllogictest.js +425 -365
- package/dist/transactions/index.js +1464 -1712
- package/dist/worker-protocol/index.js +70 -69
- package/package.json +20 -2
|
@@ -4,354 +4,320 @@ const INITIAL_KEY_CAPACITY = 32;
|
|
|
4
4
|
const INDEX_FIELDS = 4;
|
|
5
5
|
const UINT32_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
6
6
|
const INITIAL_SCRATCH_CAPACITY = 64;
|
|
7
|
-
// Every probe encodes its key into this shared scratch arena instead of allocating a fresh buffer
|
|
8
|
-
// per row. Encoding is synchronous and the bytes are consumed before the next probe begins, so a
|
|
9
|
-
// single arena serves every index; the miss path copies out of it before any caller code runs.
|
|
10
7
|
let scratch = new Uint8Array(INITIAL_SCRATCH_CAPACITY);
|
|
11
8
|
let scratchView = new DataView(scratch.buffer);
|
|
12
|
-
// The arena grows to fit the largest key ever encoded, so a single pathological key (a
|
|
13
|
-
// multi-megabyte string) would otherwise pin that memory for the life of the process — hostile
|
|
14
|
-
// to low-end devices. Each fresh encoding starts by shedding anything beyond the retention cap;
|
|
15
|
-
// ordinary keys never reach it, so the check is one branch per operation.
|
|
16
9
|
const SCRATCH_RETAIN_BYTES = 1024 * 1024;
|
|
17
10
|
function reclaimScratch() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
if (scratch.byteLength <= SCRATCH_RETAIN_BYTES)
|
|
12
|
+
return;
|
|
13
|
+
scratch = new Uint8Array(SCRATCH_RETAIN_BYTES);
|
|
14
|
+
scratchView = new DataView(scratch.buffer);
|
|
22
15
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
16
|
+
class ByteGroupIndex {
|
|
17
|
+
#memory;
|
|
18
|
+
#values = [];
|
|
19
|
+
#buckets = new Int32Array(0);
|
|
20
|
+
#hashes = new Uint32Array(0);
|
|
21
|
+
#offsets = new Uint32Array(0);
|
|
22
|
+
#lengths = new Uint32Array(0);
|
|
23
|
+
#next = new Int32Array(0);
|
|
24
|
+
#keys = new Uint8Array(0);
|
|
25
|
+
#keyLength = 0;
|
|
26
|
+
#entryReservation;
|
|
27
|
+
#bucketReservation;
|
|
28
|
+
#keyReservation;
|
|
29
|
+
constructor(memory) {
|
|
30
|
+
this.#memory = memory;
|
|
31
|
+
}
|
|
32
|
+
get size() {
|
|
33
|
+
return this.#values.length;
|
|
34
|
+
}
|
|
35
|
+
get(keys) {
|
|
36
|
+
const length = encodeGroupKey(keys);
|
|
37
|
+
const index = this.#find(length, hashScratch(length));
|
|
38
|
+
return index < 0 ? void 0 : this.#values[index];
|
|
39
|
+
}
|
|
40
|
+
getEmpty() {
|
|
41
|
+
return this.get([]);
|
|
42
|
+
}
|
|
43
|
+
getOne(key) {
|
|
44
|
+
const length = encodeSingleScalarKey(key);
|
|
45
|
+
const index = this.#find(length, hashScratch(length));
|
|
46
|
+
return index < 0 ? void 0 : this.#values[index];
|
|
47
|
+
}
|
|
48
|
+
set(keys, value) {
|
|
49
|
+
const length = encodeGroupKey(keys);
|
|
50
|
+
const hash = hashScratch(length);
|
|
51
|
+
const existing = this.#find(length, hash);
|
|
52
|
+
if (existing >= 0) {
|
|
53
|
+
this.#values[existing] = value;
|
|
54
|
+
return;
|
|
39
55
|
}
|
|
40
|
-
|
|
41
|
-
|
|
56
|
+
this.#insert(scratch.slice(0, length), hash, value);
|
|
57
|
+
}
|
|
58
|
+
getOrInsert(keys, create) {
|
|
59
|
+
const length = encodeGroupKey(keys);
|
|
60
|
+
return this.#getOrInsertScratch(length, create);
|
|
61
|
+
}
|
|
62
|
+
getOrInsertOne(key, create) {
|
|
63
|
+
const length = encodeSingleScalarKey(key);
|
|
64
|
+
return this.#getOrInsertScratch(length, create);
|
|
65
|
+
}
|
|
66
|
+
#getOrInsertScratch(length, create) {
|
|
67
|
+
const hash = hashScratch(length);
|
|
68
|
+
const existing = this.#find(length, hash);
|
|
69
|
+
if (existing >= 0)
|
|
70
|
+
return this.#values[existing];
|
|
71
|
+
const encoded = scratch.slice(0, length);
|
|
72
|
+
const value = create();
|
|
73
|
+
this.#insert(encoded, hash, value);
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
#insert(encoded, hash, value) {
|
|
77
|
+
const index = this.#values.length;
|
|
78
|
+
this.#ensureEntryCapacity(index + 1);
|
|
79
|
+
this.#ensureBucketCapacity(index + 1);
|
|
80
|
+
this.#ensureKeyCapacity(encoded.byteLength);
|
|
81
|
+
const offset = this.#keyLength;
|
|
82
|
+
this.#keys.set(encoded, offset);
|
|
83
|
+
this.#keyLength += encoded.byteLength;
|
|
84
|
+
this.#hashes[index] = hash;
|
|
85
|
+
this.#offsets[index] = offset;
|
|
86
|
+
this.#lengths[index] = encoded.byteLength;
|
|
87
|
+
const bucket = hash & this.#buckets.length - 1;
|
|
88
|
+
this.#next[index] = this.#buckets[bucket] ?? -1;
|
|
89
|
+
this.#buckets[bucket] = index;
|
|
90
|
+
this.#values.push(value);
|
|
91
|
+
}
|
|
92
|
+
setEmpty(value) {
|
|
93
|
+
this.set([], value);
|
|
94
|
+
}
|
|
95
|
+
setOne(key, value) {
|
|
96
|
+
this.set([key], value);
|
|
97
|
+
}
|
|
98
|
+
values() {
|
|
99
|
+
return this.#values;
|
|
100
|
+
}
|
|
101
|
+
#find(length, hash) {
|
|
102
|
+
if (this.#buckets.length === 0)
|
|
103
|
+
return -1;
|
|
104
|
+
let index = this.#buckets[hash & this.#buckets.length - 1] ?? -1;
|
|
105
|
+
while (index >= 0) {
|
|
106
|
+
if (this.#hashes[index] === hash && this.#lengths[index] === length && equalsScratch(this.#keys, this.#offsets[index] ?? 0, length)) {
|
|
107
|
+
return index;
|
|
108
|
+
}
|
|
109
|
+
index = this.#next[index] ?? -1;
|
|
42
110
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const length = encodeSingleScalarKey(key);
|
|
72
|
-
return this.#getOrInsertScratch(length, create);
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Resolves the key currently held in the scratch arena. A hit touches no allocation at all; a
|
|
76
|
-
* miss — at most one per distinct group — copies the key out before `create` runs, so caller
|
|
77
|
-
* code can never observe or disturb the shared arena.
|
|
78
|
-
*/
|
|
79
|
-
#getOrInsertScratch(length, create) {
|
|
80
|
-
const hash = hashScratch(length);
|
|
81
|
-
const existing = this.#find(length, hash);
|
|
82
|
-
if (existing >= 0)
|
|
83
|
-
return this.#values[existing];
|
|
84
|
-
const encoded = scratch.slice(0, length);
|
|
85
|
-
const value = create();
|
|
86
|
-
this.#insert(encoded, hash, value);
|
|
87
|
-
return value;
|
|
88
|
-
}
|
|
89
|
-
#insert(encoded, hash, value) {
|
|
90
|
-
const index = this.#values.length;
|
|
91
|
-
this.#ensureEntryCapacity(index + 1);
|
|
92
|
-
this.#ensureBucketCapacity(index + 1);
|
|
93
|
-
this.#ensureKeyCapacity(encoded.byteLength);
|
|
94
|
-
const offset = this.#keyLength;
|
|
95
|
-
this.#keys.set(encoded, offset);
|
|
96
|
-
this.#keyLength += encoded.byteLength;
|
|
97
|
-
this.#hashes[index] = hash;
|
|
98
|
-
this.#offsets[index] = offset;
|
|
99
|
-
this.#lengths[index] = encoded.byteLength;
|
|
100
|
-
const bucket = hash & (this.#buckets.length - 1);
|
|
101
|
-
this.#next[index] = this.#buckets[bucket] ?? -1;
|
|
102
|
-
this.#buckets[bucket] = index;
|
|
103
|
-
this.#values.push(value);
|
|
104
|
-
}
|
|
105
|
-
setEmpty(value) {
|
|
106
|
-
this.set([], value);
|
|
107
|
-
}
|
|
108
|
-
setOne(key, value) {
|
|
109
|
-
this.set([key], value);
|
|
110
|
-
}
|
|
111
|
-
values() {
|
|
112
|
-
return this.#values;
|
|
113
|
-
}
|
|
114
|
-
/** Matches the scratch arena's first `length` bytes against stored keys without copying them. */
|
|
115
|
-
#find(length, hash) {
|
|
116
|
-
if (this.#buckets.length === 0)
|
|
117
|
-
return -1;
|
|
118
|
-
let index = this.#buckets[hash & (this.#buckets.length - 1)] ?? -1;
|
|
119
|
-
while (index >= 0) {
|
|
120
|
-
if (this.#hashes[index] === hash &&
|
|
121
|
-
this.#lengths[index] === length &&
|
|
122
|
-
equalsScratch(this.#keys, this.#offsets[index] ?? 0, length)) {
|
|
123
|
-
return index;
|
|
124
|
-
}
|
|
125
|
-
index = this.#next[index] ?? -1;
|
|
126
|
-
}
|
|
127
|
-
return -1;
|
|
111
|
+
return -1;
|
|
112
|
+
}
|
|
113
|
+
#ensureEntryCapacity(required) {
|
|
114
|
+
if (required <= this.#hashes.length)
|
|
115
|
+
return;
|
|
116
|
+
let capacity = Math.max(INITIAL_ENTRY_CAPACITY, this.#hashes.length * 2);
|
|
117
|
+
while (capacity < required)
|
|
118
|
+
capacity = safeDouble(capacity, "Group index entries");
|
|
119
|
+
const reservation = this.#memory.reserve(safeProduct(capacity, INDEX_FIELDS * UINT32_BYTES, "Group index entry storage"), "Group index entry storage");
|
|
120
|
+
try {
|
|
121
|
+
const hashes = new Uint32Array(capacity);
|
|
122
|
+
const offsets = new Uint32Array(capacity);
|
|
123
|
+
const lengths = new Uint32Array(capacity);
|
|
124
|
+
const next = new Int32Array(capacity);
|
|
125
|
+
next.fill(-1);
|
|
126
|
+
hashes.set(this.#hashes);
|
|
127
|
+
offsets.set(this.#offsets);
|
|
128
|
+
lengths.set(this.#lengths);
|
|
129
|
+
next.set(this.#next);
|
|
130
|
+
this.#hashes = hashes;
|
|
131
|
+
this.#offsets = offsets;
|
|
132
|
+
this.#lengths = lengths;
|
|
133
|
+
this.#next = next;
|
|
134
|
+
this.#entryReservation?.release();
|
|
135
|
+
this.#entryReservation = reservation;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
reservation.release();
|
|
138
|
+
throw error;
|
|
128
139
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const hashes = new Uint32Array(capacity);
|
|
138
|
-
const offsets = new Uint32Array(capacity);
|
|
139
|
-
const lengths = new Uint32Array(capacity);
|
|
140
|
-
const next = new Int32Array(capacity);
|
|
141
|
-
next.fill(-1);
|
|
142
|
-
hashes.set(this.#hashes);
|
|
143
|
-
offsets.set(this.#offsets);
|
|
144
|
-
lengths.set(this.#lengths);
|
|
145
|
-
next.set(this.#next);
|
|
146
|
-
this.#hashes = hashes;
|
|
147
|
-
this.#offsets = offsets;
|
|
148
|
-
this.#lengths = lengths;
|
|
149
|
-
this.#next = next;
|
|
150
|
-
this.#entryReservation?.release();
|
|
151
|
-
this.#entryReservation = reservation;
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
reservation.release();
|
|
155
|
-
throw error;
|
|
156
|
-
}
|
|
140
|
+
}
|
|
141
|
+
#ensureBucketCapacity(requiredEntries) {
|
|
142
|
+
let capacity = this.#buckets.length;
|
|
143
|
+
if (capacity === 0)
|
|
144
|
+
capacity = INITIAL_BUCKET_CAPACITY;
|
|
145
|
+
while (requiredEntries * 4 > capacity * 3) {
|
|
146
|
+
capacity = safeDouble(capacity, "Group index buckets");
|
|
157
147
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
this.#buckets = buckets;
|
|
177
|
-
this.#bucketReservation?.release();
|
|
178
|
-
this.#bucketReservation = reservation;
|
|
179
|
-
}
|
|
180
|
-
catch (error) {
|
|
181
|
-
reservation.release();
|
|
182
|
-
throw error;
|
|
183
|
-
}
|
|
148
|
+
if (capacity === this.#buckets.length)
|
|
149
|
+
return;
|
|
150
|
+
const reservation = this.#memory.reserve(safeProduct(capacity, Int32Array.BYTES_PER_ELEMENT, "Group index bucket storage"), "Group index bucket storage");
|
|
151
|
+
try {
|
|
152
|
+
const buckets = new Int32Array(capacity);
|
|
153
|
+
buckets.fill(-1);
|
|
154
|
+
for (let index = 0; index < this.#values.length; index += 1) {
|
|
155
|
+
const bucket = (this.#hashes[index] ?? 0) & capacity - 1;
|
|
156
|
+
this.#next[index] = buckets[bucket] ?? -1;
|
|
157
|
+
buckets[bucket] = index;
|
|
158
|
+
}
|
|
159
|
+
this.#buckets = buckets;
|
|
160
|
+
this.#bucketReservation?.release();
|
|
161
|
+
this.#bucketReservation = reservation;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
reservation.release();
|
|
164
|
+
throw error;
|
|
184
165
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
166
|
+
}
|
|
167
|
+
#ensureKeyCapacity(additionalBytes) {
|
|
168
|
+
const required = this.#keyLength + additionalBytes;
|
|
169
|
+
if (!Number.isSafeInteger(required))
|
|
170
|
+
throw new RangeError("Group key arena is too large");
|
|
171
|
+
if (required > 4294967295)
|
|
172
|
+
throw new RangeError("Group key arena exceeds uint32 offsets");
|
|
173
|
+
if (required <= this.#keys.byteLength)
|
|
174
|
+
return;
|
|
175
|
+
let capacity = Math.max(INITIAL_KEY_CAPACITY, this.#keys.byteLength * 2);
|
|
176
|
+
while (capacity < required)
|
|
177
|
+
capacity = safeDouble(capacity, "Group key arena");
|
|
178
|
+
const reservation = this.#memory.reserve(capacity, "Group key arena");
|
|
179
|
+
try {
|
|
180
|
+
const keys = new Uint8Array(capacity);
|
|
181
|
+
keys.set(this.#keys.subarray(0, this.#keyLength));
|
|
182
|
+
this.#keys = keys;
|
|
183
|
+
this.#keyReservation?.release();
|
|
184
|
+
this.#keyReservation = reservation;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
reservation.release();
|
|
187
|
+
throw error;
|
|
208
188
|
}
|
|
189
|
+
}
|
|
209
190
|
}
|
|
210
|
-
/**
|
|
211
|
-
* Writes the canonical tagged encoding of `keys` into the scratch arena and returns its byte
|
|
212
|
-
* length. The byte layout is unchanged from the allocating encoder it replaces — a one-byte tag
|
|
213
|
-
* per key, little-endian float64 numbers, and length-prefixed UTF-8 strings — so stored keys and
|
|
214
|
-
* their hashes stay comparable across both paths.
|
|
215
|
-
*/
|
|
216
191
|
function encodeGroupKey(keys) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
192
|
+
reclaimScratch();
|
|
193
|
+
let offset = 0;
|
|
194
|
+
for (const key of keys)
|
|
195
|
+
offset = writeGroupKey(key, offset);
|
|
196
|
+
return offset;
|
|
222
197
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
* before any other key operation can run, so a single arena serves every index. NaN is the one
|
|
227
|
-
* excluded number — group callers canonicalize non-finite to null, join callers skip NaN keys —
|
|
228
|
-
* while ±Infinity encodes as an ordinary float64 so computed join keys can overflow and still match.
|
|
229
|
-
*/
|
|
230
|
-
export function encodeSingleScalarKey(key) {
|
|
231
|
-
reclaimScratch();
|
|
232
|
-
return writeGroupKey(key, 0);
|
|
198
|
+
function encodeSingleScalarKey(key) {
|
|
199
|
+
reclaimScratch();
|
|
200
|
+
return writeGroupKey(key, 0);
|
|
233
201
|
}
|
|
234
202
|
function writeGroupKey(key, offset) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
203
|
+
if (key === null) {
|
|
204
|
+
ensureScratchCapacity(offset + 1);
|
|
205
|
+
scratch[offset] = 0;
|
|
206
|
+
return offset + 1;
|
|
207
|
+
}
|
|
208
|
+
if (typeof key === "boolean") {
|
|
209
|
+
ensureScratchCapacity(offset + 1);
|
|
210
|
+
scratch[offset] = key ? 2 : 1;
|
|
211
|
+
return offset + 1;
|
|
212
|
+
}
|
|
213
|
+
if (typeof key === "number") {
|
|
214
|
+
if (Number.isNaN(key))
|
|
215
|
+
throw new TypeError("Scalar index keys cannot be NaN");
|
|
216
|
+
ensureScratchCapacity(offset + 1 + Float64Array.BYTES_PER_ELEMENT);
|
|
217
|
+
scratch[offset] = 3;
|
|
218
|
+
scratchView.setFloat64(offset + 1, key === 0 ? 0 : key, true);
|
|
219
|
+
return offset + 1 + Float64Array.BYTES_PER_ELEMENT;
|
|
220
|
+
}
|
|
221
|
+
const bound = safeSum(offset + 5, safeProduct(key.length, 3, "Encoded group key"), "Encoded group key");
|
|
222
|
+
if (bound > 4294967295)
|
|
223
|
+
throw new RangeError("Encoded group key is too large");
|
|
224
|
+
ensureScratchCapacity(bound);
|
|
225
|
+
scratch[offset] = 4;
|
|
226
|
+
const payloadStart = offset + 5;
|
|
227
|
+
const payloadEnd = writeUtf8(key, payloadStart);
|
|
228
|
+
scratchView.setUint32(offset + 1, payloadEnd - payloadStart, true);
|
|
229
|
+
return payloadEnd;
|
|
230
|
+
}
|
|
231
|
+
function writeUtf8(value, offset) {
|
|
232
|
+
let write = offset;
|
|
233
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
234
|
+
let code = value.charCodeAt(index);
|
|
235
|
+
if (code < 128) {
|
|
236
|
+
scratch[write] = code;
|
|
237
|
+
write += 1;
|
|
238
|
+
continue;
|
|
239
239
|
}
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
if (code < 2048) {
|
|
241
|
+
scratch[write] = 192 | code >>> 6;
|
|
242
|
+
scratch[write + 1] = 128 | code & 63;
|
|
243
|
+
write += 2;
|
|
244
|
+
continue;
|
|
244
245
|
}
|
|
245
|
-
if (
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
246
|
+
if (code >= 55296 && code <= 56319) {
|
|
247
|
+
const low = index + 1 < value.length ? value.charCodeAt(index + 1) : 0;
|
|
248
|
+
if (low >= 56320 && low <= 57343) {
|
|
249
|
+
code = 65536 + (code - 55296 << 10) + (low - 56320);
|
|
250
|
+
index += 1;
|
|
251
|
+
scratch[write] = 240 | code >>> 18;
|
|
252
|
+
scratch[write + 1] = 128 | code >>> 12 & 63;
|
|
253
|
+
scratch[write + 2] = 128 | code >>> 6 & 63;
|
|
254
|
+
scratch[write + 3] = 128 | code & 63;
|
|
255
|
+
write += 4;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
252
258
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
// next key operation.
|
|
256
|
-
const bound = safeSum(offset + 5, safeProduct(key.length, 3, "Encoded group key"), "Encoded group key");
|
|
257
|
-
if (bound > 0xffff_ffff)
|
|
258
|
-
throw new RangeError("Encoded group key is too large");
|
|
259
|
-
ensureScratchCapacity(bound);
|
|
260
|
-
scratch[offset] = 4;
|
|
261
|
-
const payloadStart = offset + 5;
|
|
262
|
-
const payloadEnd = writeUtf8(key, payloadStart);
|
|
263
|
-
scratchView.setUint32(offset + 1, payloadEnd - payloadStart, true);
|
|
264
|
-
return payloadEnd;
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Encodes `value` as UTF-8 in one pass. Isolated surrogates fail before the encoded bytes can
|
|
268
|
-
* participate in a lookup or insertion.
|
|
269
|
-
*/
|
|
270
|
-
function writeUtf8(value, offset) {
|
|
271
|
-
let write = offset;
|
|
272
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
273
|
-
let code = value.charCodeAt(index);
|
|
274
|
-
if (code < 0x80) {
|
|
275
|
-
scratch[write] = code;
|
|
276
|
-
write += 1;
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
if (code < 0x800) {
|
|
280
|
-
scratch[write] = 0xc0 | (code >>> 6);
|
|
281
|
-
scratch[write + 1] = 0x80 | (code & 0x3f);
|
|
282
|
-
write += 2;
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
if (code >= 0xd800 && code <= 0xdbff) {
|
|
286
|
-
const low = index + 1 < value.length ? value.charCodeAt(index + 1) : 0;
|
|
287
|
-
if (low >= 0xdc00 && low <= 0xdfff) {
|
|
288
|
-
code = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00);
|
|
289
|
-
index += 1;
|
|
290
|
-
scratch[write] = 0xf0 | (code >>> 18);
|
|
291
|
-
scratch[write + 1] = 0x80 | ((code >>> 12) & 0x3f);
|
|
292
|
-
scratch[write + 2] = 0x80 | ((code >>> 6) & 0x3f);
|
|
293
|
-
scratch[write + 3] = 0x80 | (code & 0x3f);
|
|
294
|
-
write += 4;
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (code >= 0xd800 && code <= 0xdfff) {
|
|
299
|
-
throw new TypeError(`String value contains an unpaired surrogate at UTF-16 index ${String(index)}`);
|
|
300
|
-
}
|
|
301
|
-
scratch[write] = 0xe0 | (code >>> 12);
|
|
302
|
-
scratch[write + 1] = 0x80 | ((code >>> 6) & 0x3f);
|
|
303
|
-
scratch[write + 2] = 0x80 | (code & 0x3f);
|
|
304
|
-
write += 3;
|
|
259
|
+
if (code >= 55296 && code <= 57343) {
|
|
260
|
+
throw new TypeError(`String value contains an unpaired surrogate at UTF-16 index ${String(index)}`);
|
|
305
261
|
}
|
|
306
|
-
|
|
262
|
+
scratch[write] = 224 | code >>> 12;
|
|
263
|
+
scratch[write + 1] = 128 | code >>> 6 & 63;
|
|
264
|
+
scratch[write + 2] = 128 | code & 63;
|
|
265
|
+
write += 3;
|
|
266
|
+
}
|
|
267
|
+
return write;
|
|
307
268
|
}
|
|
308
269
|
function ensureScratchCapacity(required) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
scratchView = new DataView(scratch.buffer);
|
|
270
|
+
if (required <= scratch.byteLength)
|
|
271
|
+
return;
|
|
272
|
+
let capacity = scratch.byteLength;
|
|
273
|
+
while (capacity < required)
|
|
274
|
+
capacity = safeDouble(capacity, "Group key scratch arena");
|
|
275
|
+
const grown = new Uint8Array(capacity);
|
|
276
|
+
grown.set(scratch);
|
|
277
|
+
scratch = grown;
|
|
278
|
+
scratchView = new DataView(scratch.buffer);
|
|
319
279
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return hash;
|
|
280
|
+
function hashScratch(length) {
|
|
281
|
+
let hash = 2166136261;
|
|
282
|
+
for (let index = 0; index < length; index += 1) {
|
|
283
|
+
hash = Math.imul(hash ^ (scratch[index] ?? 0), 16777619) >>> 0;
|
|
284
|
+
}
|
|
285
|
+
return hash;
|
|
327
286
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
return true;
|
|
287
|
+
function equalsScratch(arena, offset, length) {
|
|
288
|
+
for (let index = 0; index < length; index += 1) {
|
|
289
|
+
if (arena[offset + index] !== scratch[index])
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
return true;
|
|
335
293
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return scratch.slice(0, length);
|
|
294
|
+
function copyScratchKey(length) {
|
|
295
|
+
return scratch.slice(0, length);
|
|
339
296
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
297
|
+
function safeDouble(value, label) {
|
|
298
|
+
const doubled = value * 2;
|
|
299
|
+
if (!Number.isSafeInteger(doubled))
|
|
300
|
+
throw new RangeError(`${label} exceeds the safe integer range`);
|
|
301
|
+
return doubled;
|
|
345
302
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
303
|
+
function safeProduct(left, right, label) {
|
|
304
|
+
const product = left * right;
|
|
305
|
+
if (!Number.isSafeInteger(product))
|
|
306
|
+
throw new RangeError(`${label} exceeds the safe integer range`);
|
|
307
|
+
return product;
|
|
351
308
|
}
|
|
352
309
|
function safeSum(left, right, label) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
310
|
+
const sum = left + right;
|
|
311
|
+
if (!Number.isSafeInteger(sum))
|
|
312
|
+
throw new RangeError(`${label} exceeds the safe integer range`);
|
|
313
|
+
return sum;
|
|
357
314
|
}
|
|
315
|
+
export {
|
|
316
|
+
ByteGroupIndex,
|
|
317
|
+
copyScratchKey,
|
|
318
|
+
encodeSingleScalarKey,
|
|
319
|
+
equalsScratch,
|
|
320
|
+
hashScratch,
|
|
321
|
+
safeDouble,
|
|
322
|
+
safeProduct
|
|
323
|
+
};
|