@minnowdb/core 0.6.9 → 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.d.ts +5 -0
- package/dist/date-value.js +71 -41
- 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 +15952 -18465
- 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 +2642 -2515
- package/dist/engine/point-read.d.ts +4 -2
- package/dist/engine/point-read.js +121 -171
- package/dist/engine/query-api.js +12 -3
- package/dist/engine/query-cache.js +61 -69
- package/dist/engine/query.d.ts +45 -0
- package/dist/engine/query.js +8506 -9375
- 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.d.ts +7 -1
- package/dist/engine/sql-domains.js +648 -734
- package/dist/engine/sql-driver.js +0 -1
- package/dist/engine/sql-functions.d.ts +11 -0
- package/dist/engine/sql-functions.js +1092 -0
- package/dist/engine/sql-json.js +199 -218
- package/dist/engine/sql-semantics.d.ts +25 -4
- package/dist/engine/sql-semantics.js +579 -513
- package/dist/engine/typed-live.js +271 -293
- package/dist/engine/vector.d.ts +7 -1
- package/dist/engine/vector.js +4513 -4580
- 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 +9 -1
- 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 +2952 -3228
- 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
- package/postgres-feature-profile.json +15 -5
- package/sql-feature-matrix.json +252 -2
|
@@ -1,50 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const count = handle.write(chunk, { at: at + transferred });
|
|
12
|
-
assertTransferCount(count, remaining, "write", operation, at + transferred);
|
|
13
|
-
if (count === 0) {
|
|
14
|
-
throw new Error(`OPFS write made no progress while ${operation}: wrote ${String(transferred)} of ` +
|
|
15
|
-
`${String(buffer.byteLength)} bytes (stalled at file offset ${String(at + transferred)})`);
|
|
16
|
-
}
|
|
17
|
-
transferred += count;
|
|
1
|
+
function writeFully(handle, buffer, at, operation = "writing file bytes") {
|
|
2
|
+
assertTransferRange(at, buffer.byteLength, "write", operation);
|
|
3
|
+
let transferred = 0;
|
|
4
|
+
while (transferred < buffer.byteLength) {
|
|
5
|
+
const remaining = buffer.byteLength - transferred;
|
|
6
|
+
const chunk = transferred === 0 ? buffer : buffer.subarray(transferred);
|
|
7
|
+
const count = handle.write(chunk, { at: at + transferred });
|
|
8
|
+
assertTransferCount(count, remaining, "write", operation, at + transferred);
|
|
9
|
+
if (count === 0) {
|
|
10
|
+
throw new Error(`OPFS write made no progress while ${operation}: wrote ${String(transferred)} of ${String(buffer.byteLength)} bytes (stalled at file offset ${String(at + transferred)})`);
|
|
18
11
|
}
|
|
12
|
+
transferred += count;
|
|
13
|
+
}
|
|
19
14
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const count = handle.read(chunk, { at: at + transferred });
|
|
31
|
-
assertTransferCount(count, remaining, "read", operation, at + transferred);
|
|
32
|
-
if (count === 0) {
|
|
33
|
-
throw new Error(`Unexpected EOF while ${operation}: read ${String(transferred)} of ` +
|
|
34
|
-
`${String(buffer.byteLength)} bytes (EOF at file offset ${String(at + transferred)})`);
|
|
35
|
-
}
|
|
36
|
-
transferred += count;
|
|
15
|
+
function readFully(handle, buffer, at, operation = "reading file bytes") {
|
|
16
|
+
assertTransferRange(at, buffer.byteLength, "read", operation);
|
|
17
|
+
let transferred = 0;
|
|
18
|
+
while (transferred < buffer.byteLength) {
|
|
19
|
+
const remaining = buffer.byteLength - transferred;
|
|
20
|
+
const chunk = transferred === 0 ? buffer : buffer.subarray(transferred);
|
|
21
|
+
const count = handle.read(chunk, { at: at + transferred });
|
|
22
|
+
assertTransferCount(count, remaining, "read", operation, at + transferred);
|
|
23
|
+
if (count === 0) {
|
|
24
|
+
throw new Error(`Unexpected EOF while ${operation}: read ${String(transferred)} of ${String(buffer.byteLength)} bytes (EOF at file offset ${String(at + transferred)})`);
|
|
37
25
|
}
|
|
26
|
+
transferred += count;
|
|
27
|
+
}
|
|
38
28
|
}
|
|
39
29
|
function assertTransferRange(at, length, kind, operation) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
30
|
+
if (!Number.isSafeInteger(at) || at < 0 || length > Number.MAX_SAFE_INTEGER - at) {
|
|
31
|
+
throw new RangeError(`Invalid OPFS ${kind} range while ${operation}: ${String(at)}+${String(length)} must be a non-negative safe-integer byte range`);
|
|
32
|
+
}
|
|
44
33
|
}
|
|
45
34
|
function assertTransferCount(count, remaining, kind, operation, at) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
35
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > remaining) {
|
|
36
|
+
throw new Error(`Invalid OPFS ${kind} result while ${operation}: handle reported ${String(count)} bytes for a ${String(remaining)}-byte request at file offset ${String(at)}`);
|
|
37
|
+
}
|
|
50
38
|
}
|
|
39
|
+
export {
|
|
40
|
+
readFully,
|
|
41
|
+
writeFully
|
|
42
|
+
};
|
|
@@ -1,141 +1,106 @@
|
|
|
1
1
|
import { crc32 } from "../../block-format/index.js";
|
|
2
2
|
import { decodeRecordJson, encodeRecordJson } from "./wire.js";
|
|
3
3
|
import { readFully, writeFully } from "./sync-file.js";
|
|
4
|
-
|
|
5
|
-
* The write-ahead log, framed over one synchronous file handle its single writer holds open.
|
|
6
|
-
*
|
|
7
|
-
* Frame layout, little-endian: `u32 magic | u32 payloadLength | u32 crc32(payload) | payload`.
|
|
8
|
-
* An append is one complete synchronous transfer at the tail — normally one microsecond-scale
|
|
9
|
-
* write on a held handle, with retries if the platform reports a short transfer. The only
|
|
10
|
-
* artifact a crash can leave is a truncated final frame, which reads as "not written". A
|
|
11
|
-
* complete frame with foreign marker or checksum-invalid bytes is corruption and fails closed.
|
|
12
|
-
* Frames carry a global sequence number inside the payload; the file is truncated to zero only
|
|
13
|
-
* after a checkpoint covering every frame has been flushed, so replay is always
|
|
14
|
-
* newest-checkpoint-plus-tail.
|
|
15
|
-
*/
|
|
16
|
-
const FRAME_MAGIC = 0x4c574e4d; // "MNWL"
|
|
4
|
+
const FRAME_MAGIC = 1280790093;
|
|
17
5
|
const FRAME_HEADER_BYTES = 12;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export class WalWriter {
|
|
26
|
-
#handle;
|
|
27
|
-
#offset;
|
|
28
|
-
constructor(handle, offset) {
|
|
29
|
-
if (!Number.isSafeInteger(offset) || offset < 0) {
|
|
30
|
-
throw new RangeError("WAL offset is outside the safe integer range");
|
|
31
|
-
}
|
|
32
|
-
this.#handle = handle;
|
|
33
|
-
this.#offset = offset;
|
|
6
|
+
const MAX_WAL_FRAME_BYTES = 64 * 1024 * 1024;
|
|
7
|
+
class WalWriter {
|
|
8
|
+
#handle;
|
|
9
|
+
#offset;
|
|
10
|
+
constructor(handle, offset) {
|
|
11
|
+
if (!Number.isSafeInteger(offset) || offset < 0) {
|
|
12
|
+
throw new RangeError("WAL offset is outside the safe integer range");
|
|
34
13
|
}
|
|
35
|
-
|
|
36
|
-
|
|
14
|
+
this.#handle = handle;
|
|
15
|
+
this.#offset = offset;
|
|
16
|
+
}
|
|
17
|
+
get byteLength() {
|
|
18
|
+
return this.#offset;
|
|
19
|
+
}
|
|
20
|
+
append(payload, flush) {
|
|
21
|
+
const bytes = encodeRecordJson(payload);
|
|
22
|
+
if (bytes.byteLength > MAX_WAL_FRAME_BYTES) {
|
|
23
|
+
throw new RangeError(`WAL frame payload exceeds the ${String(MAX_WAL_FRAME_BYTES)} byte limit: ` + String(bytes.byteLength));
|
|
37
24
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (bytes.byteLength > MAX_WAL_FRAME_BYTES) {
|
|
42
|
-
throw new RangeError(`WAL frame payload exceeds the ${String(MAX_WAL_FRAME_BYTES)} byte limit: ` +
|
|
43
|
-
String(bytes.byteLength));
|
|
44
|
-
}
|
|
45
|
-
const frame = new Uint8Array(FRAME_HEADER_BYTES + bytes.byteLength);
|
|
46
|
-
if (this.#offset > Number.MAX_SAFE_INTEGER - frame.byteLength) {
|
|
47
|
-
throw new RangeError("WAL offset exceeds the safe integer range");
|
|
48
|
-
}
|
|
49
|
-
const view = new DataView(frame.buffer);
|
|
50
|
-
view.setUint32(0, FRAME_MAGIC, true);
|
|
51
|
-
view.setUint32(4, bytes.byteLength, true);
|
|
52
|
-
view.setUint32(8, crc32(bytes), true);
|
|
53
|
-
frame.set(bytes, FRAME_HEADER_BYTES);
|
|
54
|
-
const start = this.#offset;
|
|
55
|
-
try {
|
|
56
|
-
writeFully(this.#handle, frame, start, "appending a WAL frame");
|
|
57
|
-
if (flush)
|
|
58
|
-
this.#handle.flush();
|
|
59
|
-
this.#offset = start + frame.byteLength;
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
// A complete write followed by a failed flush is not an acknowledged frame either.
|
|
63
|
-
// Remove it before unpublished extent bytes are rolled back. If truncation itself
|
|
64
|
-
// fails, recovery will fail closed when it verifies the frame's referenced payloads.
|
|
65
|
-
try {
|
|
66
|
-
this.#handle.truncate(start);
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
// Preserve the original quota/I/O error.
|
|
70
|
-
}
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
25
|
+
const frame = new Uint8Array(FRAME_HEADER_BYTES + bytes.byteLength);
|
|
26
|
+
if (this.#offset > Number.MAX_SAFE_INTEGER - frame.byteLength) {
|
|
27
|
+
throw new RangeError("WAL offset exceeds the safe integer range");
|
|
73
28
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
29
|
+
const view = new DataView(frame.buffer);
|
|
30
|
+
view.setUint32(0, FRAME_MAGIC, true);
|
|
31
|
+
view.setUint32(4, bytes.byteLength, true);
|
|
32
|
+
view.setUint32(8, crc32(bytes), true);
|
|
33
|
+
frame.set(bytes, FRAME_HEADER_BYTES);
|
|
34
|
+
const start = this.#offset;
|
|
35
|
+
try {
|
|
36
|
+
writeFully(this.#handle, frame, start, "appending a WAL frame");
|
|
37
|
+
if (flush)
|
|
81
38
|
this.#handle.flush();
|
|
39
|
+
this.#offset = start + frame.byteLength;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
try {
|
|
42
|
+
this.#handle.truncate(start);
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
82
46
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
47
|
+
}
|
|
48
|
+
reset() {
|
|
49
|
+
this.#handle.truncate(0);
|
|
50
|
+
this.#offset = 0;
|
|
51
|
+
this.#handle.flush();
|
|
52
|
+
}
|
|
53
|
+
flush() {
|
|
54
|
+
this.#handle.flush();
|
|
55
|
+
}
|
|
56
|
+
close() {
|
|
57
|
+
this.#handle.close();
|
|
58
|
+
}
|
|
89
59
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
for (const frame of iterateWalFrames(handle)) {
|
|
101
|
-
payloads.push(frame.payload);
|
|
102
|
-
frameEnds.push(frame.frameEnd);
|
|
103
|
-
endOffset = frame.frameEnd;
|
|
104
|
-
}
|
|
105
|
-
return { payloads, frameEnds, endOffset };
|
|
60
|
+
function replayWalFrames(handle) {
|
|
61
|
+
const payloads = [];
|
|
62
|
+
const frameEnds = [];
|
|
63
|
+
let endOffset = 0;
|
|
64
|
+
for (const frame of iterateWalFrames(handle)) {
|
|
65
|
+
payloads.push(frame.payload);
|
|
66
|
+
frameEnds.push(frame.frameEnd);
|
|
67
|
+
endOffset = frame.frameEnd;
|
|
68
|
+
}
|
|
69
|
+
return { payloads, frameEnds, endOffset };
|
|
106
70
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
71
|
+
function* iterateWalFrames(handle) {
|
|
72
|
+
const size = handle.getSize();
|
|
73
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
74
|
+
throw new Error(`Invalid WAL byte length: ${String(size)}`);
|
|
75
|
+
}
|
|
76
|
+
const header = new Uint8Array(FRAME_HEADER_BYTES);
|
|
77
|
+
const headerView = new DataView(header.buffer);
|
|
78
|
+
let offset = 0;
|
|
79
|
+
while (offset + FRAME_HEADER_BYTES <= size) {
|
|
80
|
+
readFully(handle, header, offset, "reading a WAL frame header for recovery");
|
|
81
|
+
if (headerView.getUint32(0, true) !== FRAME_MAGIC) {
|
|
82
|
+
throw new Error(`WAL frame marker mismatch at offset ${String(offset)}`);
|
|
83
|
+
}
|
|
84
|
+
const length = headerView.getUint32(4, true);
|
|
85
|
+
const checksum = headerView.getUint32(8, true);
|
|
86
|
+
if (length > MAX_WAL_FRAME_BYTES) {
|
|
87
|
+
throw new Error(`WAL frame at offset ${String(offset)} exceeds the ${String(MAX_WAL_FRAME_BYTES)} byte limit: ${String(length)}`);
|
|
115
88
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
const length = headerView.getUint32(4, true);
|
|
125
|
-
const checksum = headerView.getUint32(8, true);
|
|
126
|
-
if (length > MAX_WAL_FRAME_BYTES) {
|
|
127
|
-
throw new Error(`WAL frame at offset ${String(offset)} exceeds the ${String(MAX_WAL_FRAME_BYTES)} ` +
|
|
128
|
-
`byte limit: ${String(length)}`);
|
|
129
|
-
}
|
|
130
|
-
const end = offset + FRAME_HEADER_BYTES + length;
|
|
131
|
-
if (!Number.isSafeInteger(end) || end > size)
|
|
132
|
-
break;
|
|
133
|
-
const payloadBytes = new Uint8Array(length);
|
|
134
|
-
readFully(handle, payloadBytes, offset + FRAME_HEADER_BYTES, "reading a WAL frame payload for recovery");
|
|
135
|
-
if (crc32(payloadBytes) !== checksum) {
|
|
136
|
-
throw new Error(`WAL frame checksum mismatch at offset ${String(offset)}`);
|
|
137
|
-
}
|
|
138
|
-
yield { payload: decodeRecordJson(payloadBytes), frameEnd: end };
|
|
139
|
-
offset = end;
|
|
89
|
+
const end = offset + FRAME_HEADER_BYTES + length;
|
|
90
|
+
if (!Number.isSafeInteger(end) || end > size)
|
|
91
|
+
break;
|
|
92
|
+
const payloadBytes = new Uint8Array(length);
|
|
93
|
+
readFully(handle, payloadBytes, offset + FRAME_HEADER_BYTES, "reading a WAL frame payload for recovery");
|
|
94
|
+
if (crc32(payloadBytes) !== checksum) {
|
|
95
|
+
throw new Error(`WAL frame checksum mismatch at offset ${String(offset)}`);
|
|
140
96
|
}
|
|
97
|
+
yield { payload: decodeRecordJson(payloadBytes), frameEnd: end };
|
|
98
|
+
offset = end;
|
|
99
|
+
}
|
|
141
100
|
}
|
|
101
|
+
export {
|
|
102
|
+
MAX_WAL_FRAME_BYTES,
|
|
103
|
+
WalWriter,
|
|
104
|
+
iterateWalFrames,
|
|
105
|
+
replayWalFrames
|
|
106
|
+
};
|