flatsql 2.0.2 → 3.0.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.
Files changed (84) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +76 -64
  3. package/dist/artifacts/index.d.ts +1 -3
  4. package/dist/artifacts/index.d.ts.map +1 -1
  5. package/dist/artifacts/index.js +1 -3
  6. package/dist/artifacts/index.js.map +1 -1
  7. package/dist/response/artifact.d.ts +1 -2
  8. package/dist/response/artifact.d.ts.map +1 -1
  9. package/dist/response/artifact.js.map +1 -1
  10. package/dist/response/types.d.ts +6 -0
  11. package/dist/response/types.d.ts.map +1 -1
  12. package/dist/stress/index.d.ts +0 -1
  13. package/dist/stress/index.d.ts.map +1 -1
  14. package/dist/stress/index.js +0 -1
  15. package/dist/stress/index.js.map +1 -1
  16. package/dist/stress/native-publish-event-workload.d.ts.map +1 -1
  17. package/dist/stress/native-publish-event-workload.js +14 -4
  18. package/dist/stress/native-publish-event-workload.js.map +1 -1
  19. package/dist/stress/smoke-runner.d.ts.map +1 -1
  20. package/dist/stress/smoke-runner.js +6 -5
  21. package/dist/stress/smoke-runner.js.map +1 -1
  22. package/docs/PARTITION-STORE-WASM.md +241 -0
  23. package/docs/PARTITION-STORE.md +635 -0
  24. package/package.json +61 -22
  25. package/wasm/flatsql-io.js +4 -1
  26. package/wasm/flatsql-ps-threads.wasm +0 -0
  27. package/wasm/flatsql-wasi-noeh.wasm +0 -0
  28. package/wasm/flatsql-wasi.wasm +0 -0
  29. package/wasm/flatsql.js +1 -1
  30. package/wasm/flatsql.wasm +0 -0
  31. package/wasm/index.d.ts +2 -0
  32. package/wasm/index.js +16 -1
  33. package/wasm/integrity.json +28 -0
  34. package/wasm/ps-node-fault.d.mts +23 -0
  35. package/wasm/ps-node-fault.mjs +381 -0
  36. package/wasm/ps-node-guest.mjs +156 -0
  37. package/wasm/ps-node-host.d.mts +86 -0
  38. package/wasm/ps-node-host.mjs +431 -0
  39. package/wasm/ps-node-imports.mjs +847 -0
  40. package/wasm/ps.d.ts +21 -0
  41. package/wasm/ps.js +75 -0
  42. package/dist/artifacts/builder.d.ts +0 -31
  43. package/dist/artifacts/builder.d.ts.map +0 -1
  44. package/dist/artifacts/builder.js +0 -514
  45. package/dist/artifacts/builder.js.map +0 -1
  46. package/dist/artifacts/demo-extractors.d.ts +0 -15
  47. package/dist/artifacts/demo-extractors.d.ts.map +0 -1
  48. package/dist/artifacts/demo-extractors.js +0 -238
  49. package/dist/artifacts/demo-extractors.js.map +0 -1
  50. package/dist/artifacts/standalone-builder.d.ts +0 -62
  51. package/dist/artifacts/standalone-builder.d.ts.map +0 -1
  52. package/dist/artifacts/standalone-builder.js +0 -144
  53. package/dist/artifacts/standalone-builder.js.map +0 -1
  54. package/dist/artifacts/worker-client.d.ts +0 -32
  55. package/dist/artifacts/worker-client.d.ts.map +0 -1
  56. package/dist/artifacts/worker-client.js +0 -415
  57. package/dist/artifacts/worker-client.js.map +0 -1
  58. package/dist/core/database.d.ts +0 -86
  59. package/dist/core/database.d.ts.map +0 -1
  60. package/dist/core/database.js +0 -900
  61. package/dist/core/database.js.map +0 -1
  62. package/dist/core/index.d.ts +0 -3
  63. package/dist/core/index.d.ts.map +0 -1
  64. package/dist/core/index.js +0 -3
  65. package/dist/core/index.js.map +0 -1
  66. package/dist/core/table-store.d.ts +0 -30
  67. package/dist/core/table-store.d.ts.map +0 -1
  68. package/dist/core/table-store.js +0 -187
  69. package/dist/core/table-store.js.map +0 -1
  70. package/dist/flatbuffer-accessor.d.ts +0 -35
  71. package/dist/flatbuffer-accessor.d.ts.map +0 -1
  72. package/dist/flatbuffer-accessor.js +0 -114
  73. package/dist/flatbuffer-accessor.js.map +0 -1
  74. package/dist/index.d.ts +0 -15
  75. package/dist/index.d.ts.map +0 -1
  76. package/dist/index.js +0 -22
  77. package/dist/index.js.map +0 -1
  78. package/dist/stress/publish-event-workload.d.ts +0 -3
  79. package/dist/stress/publish-event-workload.d.ts.map +0 -1
  80. package/dist/stress/publish-event-workload.js +0 -467
  81. package/dist/stress/publish-event-workload.js.map +0 -1
  82. package/wasm/flatsql-artifact.worker.js +0 -847
  83. package/wasm/standalone.d.ts +0 -122
  84. package/wasm/standalone.js +0 -914
package/wasm/ps.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ // Types for wasm/ps.js (the partition store artifact, flatsql-ps-threads.wasm).
2
+
3
+ export const FLATSQL_PS_THREADS_FILE: 'flatsql-ps-threads.wasm';
4
+
5
+ /** URL of the packaged flatsql-ps-threads.wasm. */
6
+ export function getFlatSQLPsThreadsURL(): URL;
7
+
8
+ export interface LoadFlatSQLPsThreadsOptions {
9
+ url?: string | URL;
10
+ path?: string;
11
+ /** false skips the integrity.json check. */
12
+ verify?: boolean;
13
+ /** SHA-256 (hex) provider outside Node; back it with WASM or native crypto. Browser WebCrypto is intentionally not used. */
14
+ computeSHA256?: (bytes: Uint8Array) => string | Promise<string>;
15
+ }
16
+
17
+ /** The artifact's bytes, checked against integrity.json (Node, or with computeSHA256). */
18
+ export function loadFlatSQLPsThreads(options?: LoadFlatSQLPsThreadsOptions): Promise<Uint8Array>;
19
+
20
+ /** The artifact's sha256 (hex) from the packaged integrity.json, or null. */
21
+ export function flatsqlPsThreadsSha256(): Promise<string | null>;
package/wasm/ps.js ADDED
@@ -0,0 +1,75 @@
1
+ // The partition store artifact, flatsql-ps-threads.wasm (docs/PARTITION-STORE-WASM.md).
2
+ //
3
+ // A wasm32-wasip1-threads reactor: hosts instantiate it once per writer or
4
+ // reader instance over a shared env.memory, provide WASI preview1,
5
+ // wasi.thread-spawn and the seven env.flatsql_io_* imports, and drive it
6
+ // through the flatsql_ps_* C ABI (cpp/include/flatsql/ps/flatsql_ps.h).
7
+ // Node: flatsql/ps/node-host. Go/WasmEdge: the SDN host. Browsers: sdn-js.
8
+
9
+ const PS_THREADS_URL = new URL('./flatsql-ps-threads.wasm', import.meta.url);
10
+
11
+ /** File name of the artifact next to this module. */
12
+ export const FLATSQL_PS_THREADS_FILE = 'flatsql-ps-threads.wasm';
13
+
14
+ /** URL of the packaged flatsql-ps-threads.wasm. */
15
+ export function getFlatSQLPsThreadsURL() {
16
+ return PS_THREADS_URL;
17
+ }
18
+
19
+ /**
20
+ * The artifact's bytes, checked against wasm/integrity.json: in Node with
21
+ * node:crypto, elsewhere with `computeSHA256` (bytes -> hex), which the caller
22
+ * backs with WASM or native crypto (browser WebCrypto is intentionally not
23
+ * used, as in wasm/index.js). A mismatch throws.
24
+ *
25
+ * @param {{ url?: string | URL, path?: string, verify?: boolean,
26
+ * computeSHA256?: (bytes: Uint8Array) => string | Promise<string> }} [options]
27
+ * @returns {Promise<Uint8Array>}
28
+ */
29
+ export async function loadFlatSQLPsThreads(options = {}) {
30
+ let bytes;
31
+ const url = options.url ? new URL(options.url, import.meta.url) : PS_THREADS_URL;
32
+ if (options.path || url.protocol === 'file:') {
33
+ const [{ readFile }, { fileURLToPath }] = await Promise.all([import('node:fs/promises'), import('node:url')]);
34
+ bytes = new Uint8Array(await readFile(options.path ?? fileURLToPath(url)));
35
+ } else {
36
+ const response = await fetch(url);
37
+ if (!response.ok) throw new Error(`Failed to load ${FLATSQL_PS_THREADS_FILE}: ${response.status}`);
38
+ bytes = new Uint8Array(await response.arrayBuffer());
39
+ }
40
+ if (options.verify === false || options.url || options.path) return bytes;
41
+ const hash = options.computeSHA256 ?? (await nodeSha256());
42
+ if (!hash) return bytes;
43
+ const expected = await flatsqlPsThreadsSha256();
44
+ if (!expected) throw new Error(`integrity.json has no sha256 for ${FLATSQL_PS_THREADS_FILE}`);
45
+ const actual = await hash(bytes);
46
+ if (actual !== expected) {
47
+ throw new Error(`${FLATSQL_PS_THREADS_FILE}: sha256 ${actual} does not match integrity.json (${expected})`);
48
+ }
49
+ return bytes;
50
+ }
51
+
52
+ /** The artifact's sha256 (hex) from the packaged integrity.json, or null. */
53
+ export async function flatsqlPsThreadsSha256() {
54
+ const integrityUrl = new URL('./integrity.json', import.meta.url);
55
+ let text;
56
+ try {
57
+ if (integrityUrl.protocol === 'file:') {
58
+ const [{ readFile }, { fileURLToPath }] = await Promise.all([import('node:fs/promises'), import('node:url')]);
59
+ text = await readFile(fileURLToPath(integrityUrl), 'utf8');
60
+ } else {
61
+ const response = await fetch(integrityUrl);
62
+ if (!response.ok) return null;
63
+ text = await response.text();
64
+ }
65
+ } catch {
66
+ return null;
67
+ }
68
+ return JSON.parse(text)?.files?.[FLATSQL_PS_THREADS_FILE]?.sha256 ?? null;
69
+ }
70
+
71
+ async function nodeSha256() {
72
+ if (!globalThis.process?.versions?.node) return null;
73
+ const { createHash } = await import('node:crypto');
74
+ return (bytes) => createHash('sha256').update(bytes).digest('hex');
75
+ }
@@ -1,31 +0,0 @@
1
- import { type DatabaseSchema } from '../schema/index.js';
2
- import { type ArtifactFieldExtractor } from './demo-extractors.js';
3
- import type { ArtifactBuilderOptions, ArtifactIngestOptions, ArtifactQueryParams, ArtifactQuerySpec, ArtifactIngestResult, ArtifactQueryResult } from './types.js';
4
- export declare class FlatSQLArtifactBuilder {
5
- private readonly schema;
6
- private readonly db;
7
- private readonly beginTransactionSql;
8
- private readonly tableByName;
9
- private readonly fileIdToTable;
10
- private readonly extractors;
11
- private readonly queryCache;
12
- private readonly queryResultCache;
13
- private sequence;
14
- static fromSchema(source: string, options: ArtifactBuilderOptions): FlatSQLArtifactBuilder;
15
- constructor(schema: DatabaseSchema, options: ArtifactBuilderOptions);
16
- registerFileId(fileId: string, tableName: string): void;
17
- setFieldExtractor(tableName: string, extractor: ArtifactFieldExtractor): void;
18
- enableDemoExtractors(): void;
19
- ingestBuffers(buffers: Uint8Array[], options?: ArtifactIngestOptions): ArtifactIngestResult;
20
- query(sql: string, params?: ArtifactQueryParams): ArtifactQueryResult;
21
- queryMany(queries: readonly ArtifactQuerySpec[]): ArtifactQueryResult[];
22
- close(): void;
23
- private createIndexTables;
24
- private indexTableName;
25
- private recordTableName;
26
- private coveringIndexName;
27
- private createCoveringIndexes;
28
- private dropCoveringIndexes;
29
- private createIngestPlan;
30
- }
31
- //# sourceMappingURL=builder.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/artifacts/builder.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,cAAc,EAEpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EAEpB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAyYpB,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;IAClC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA+B;IAC3D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6C;IACxE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAItB;IACL,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA0C;IAC3E,OAAO,CAAC,QAAQ,CAAK;IAErB,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,GAAG,sBAAsB;gBAW9E,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,sBAAsB;IAcnE,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAIvD,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,sBAAsB,GAAG,IAAI;IAI7E,oBAAoB,IAAI,IAAI;IAM5B,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,OAAO,GAAE,qBAA0B,GAAG,oBAAoB;IA6C/F,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,mBAAmB,GAAG,mBAAmB;IAyCrE,SAAS,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,GAAG,mBAAmB,EAAE;IAIvE,KAAK,IAAI,IAAI;IAMb,OAAO,CAAC,iBAAiB;IA6BzB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,gBAAgB;CAyCzB"}
@@ -1,514 +0,0 @@
1
- import { availableParallelism } from 'node:os';
2
- import { DatabaseSync } from 'node:sqlite';
3
- import { parseSchema, SQLColumnType, } from '../schema/index.js';
4
- import { createArtifactFieldAppender, createArtifactFieldValueReader, demoExtractors, } from './demo-extractors.js';
5
- function toSqliteType(column) {
6
- switch (column.sqlType) {
7
- case SQLColumnType.INTEGER:
8
- return 'INTEGER';
9
- case SQLColumnType.REAL:
10
- return 'REAL';
11
- case SQLColumnType.TEXT:
12
- return 'TEXT';
13
- case SQLColumnType.BLOB:
14
- return 'BLOB';
15
- default:
16
- return 'BLOB';
17
- }
18
- }
19
- const decoder = new TextDecoder();
20
- const INSERT_BATCH_SIZE = 64;
21
- const DEFAULT_PAGE_SIZE = 32768;
22
- const DEFAULT_MMAP_SIZE = 268435456;
23
- const DEFAULT_CACHE_SIZE = -131072;
24
- const DEFAULT_THREAD_COUNT = Math.min(4, Math.max(2, availableParallelism()));
25
- const MAX_QUERY_STATEMENT_CACHE_ENTRIES = 256;
26
- const MAX_QUERY_RESULT_CACHE_ENTRIES = 1024;
27
- const MAX_QUERY_RESULT_CACHE_ROWS = 1000;
28
- const schemaCache = new Map();
29
- const VOLATILE_QUERY_PATTERNS = [
30
- /\bRANDOM\s*\(/i,
31
- /\bCHANGES\s*\(/i,
32
- /\bTOTAL_CHANGES\s*\(/i,
33
- /\bLAST_INSERT_ROWID\s*\(/i,
34
- /\bCURRENT_(TIME|DATE|TIMESTAMP)\b/i,
35
- /\bDATE\s*\(/i,
36
- /\bTIME\s*\(/i,
37
- /\bDATETIME\s*\(/i,
38
- /\bJULIANDAY\s*\(/i,
39
- /\bUNIXEPOCH\s*\(/i,
40
- /\bSTRFTIME\s*\(/i,
41
- ];
42
- function isCacheableQuerySql(sql) {
43
- const trimmed = sql.trimStart();
44
- if (trimmed.length === 0) {
45
- return false;
46
- }
47
- const upper = trimmed.toUpperCase();
48
- if (upper.startsWith('SELECT') || upper.startsWith('WITH')) {
49
- return true;
50
- }
51
- return upper.startsWith('PRAGMA') && !trimmed.includes('=');
52
- }
53
- function isResultCacheableQuerySql(sql) {
54
- return isCacheableQuerySql(sql) && !VOLATILE_QUERY_PATTERNS.some((pattern) => pattern.test(sql));
55
- }
56
- function normalizeRow(row, columns) {
57
- return columns.map((column) => row[column]);
58
- }
59
- function cloneQueryResult(result) {
60
- return {
61
- columns: [...result.columns],
62
- rows: result.rows.map((row) => [...row]),
63
- rowCount: result.rowCount,
64
- };
65
- }
66
- function encodeCacheValue(value) {
67
- if (value === null) {
68
- return { type: 'null' };
69
- }
70
- switch (typeof value) {
71
- case 'string':
72
- return { type: 'string', value };
73
- case 'number':
74
- return { type: 'number', value: Object.is(value, -0) ? '-0' : String(value) };
75
- case 'bigint':
76
- return { type: 'bigint', value: value.toString() };
77
- case 'boolean':
78
- return { type: 'boolean', value };
79
- case 'undefined':
80
- return { type: 'undefined' };
81
- default:
82
- return null;
83
- }
84
- }
85
- function buildResultCacheKey(sql, params) {
86
- if (!isResultCacheableQuerySql(sql)) {
87
- return null;
88
- }
89
- if (params === undefined) {
90
- return sql;
91
- }
92
- if (Array.isArray(params)) {
93
- const encoded = params.map(encodeCacheValue);
94
- if (encoded.some((value) => value === null)) {
95
- return null;
96
- }
97
- return JSON.stringify({ sql, params: { type: 'array', values: encoded } });
98
- }
99
- const prototype = Object.getPrototypeOf(params);
100
- if (prototype !== Object.prototype && prototype !== null) {
101
- return null;
102
- }
103
- const namedParams = params;
104
- const encodedEntries = [];
105
- const keys = Object.keys(namedParams).sort();
106
- for (const key of keys) {
107
- const encodedValue = encodeCacheValue(namedParams[key]);
108
- if (encodedValue === null) {
109
- return null;
110
- }
111
- encodedEntries.push({ key, value: encodedValue });
112
- }
113
- return JSON.stringify({ sql, params: { type: 'object', entries: encodedEntries } });
114
- }
115
- function getCachedResult(cache, key) {
116
- const cached = cache.get(key);
117
- if (!cached) {
118
- return undefined;
119
- }
120
- cache.delete(key);
121
- cache.set(key, cached);
122
- return cached;
123
- }
124
- function setCachedResult(cache, key, result) {
125
- if (result.rowCount > MAX_QUERY_RESULT_CACHE_ROWS) {
126
- cache.delete(key);
127
- return;
128
- }
129
- cache.set(key, cloneQueryResult(result));
130
- while (cache.size > MAX_QUERY_RESULT_CACHE_ENTRIES) {
131
- const oldest = cache.keys().next().value;
132
- if (oldest === undefined) {
133
- break;
134
- }
135
- cache.delete(oldest);
136
- }
137
- }
138
- function getCachedStatement(cache, key) {
139
- const cached = cache.get(key);
140
- if (!cached) {
141
- return undefined;
142
- }
143
- cache.delete(key);
144
- cache.set(key, cached);
145
- return cached;
146
- }
147
- function setCachedStatement(cache, key, value) {
148
- cache.set(key, value);
149
- while (cache.size > MAX_QUERY_STATEMENT_CACHE_ENTRIES) {
150
- const oldest = cache.keys().next().value;
151
- if (oldest === undefined) {
152
- break;
153
- }
154
- cache.delete(oldest);
155
- }
156
- }
157
- function executeStatementAll(statement, params) {
158
- if (params === undefined) {
159
- return statement.all();
160
- }
161
- if (Array.isArray(params)) {
162
- switch (params.length) {
163
- case 0:
164
- return statement.all();
165
- case 1:
166
- return statement.all(params[0]);
167
- case 2:
168
- return statement.all(params[0], params[1]);
169
- case 3:
170
- return statement.all(params[0], params[1], params[2]);
171
- case 4:
172
- return statement.all(params[0], params[1], params[2], params[3]);
173
- default:
174
- return statement.all(...params);
175
- }
176
- }
177
- return statement.all(params);
178
- }
179
- function readFileId(data) {
180
- if (data.length < 8) {
181
- throw new Error('FlatBuffer payload is too short to contain a file identifier');
182
- }
183
- return decoder.decode(data.subarray(4, 8));
184
- }
185
- function readFileIdCode(data) {
186
- if (data.length < 8) {
187
- throw new Error('FlatBuffer payload is too short to contain a file identifier');
188
- }
189
- return (data[4] |
190
- (data[5] << 8) |
191
- (data[6] << 16) |
192
- (data[7] << 24)) >>> 0;
193
- }
194
- function encodeFileId(fileId) {
195
- if (fileId.length !== 4) {
196
- throw new Error('FlatBuffer file identifiers must be four characters');
197
- }
198
- return (fileId.charCodeAt(0) |
199
- (fileId.charCodeAt(1) << 8) |
200
- (fileId.charCodeAt(2) << 16) |
201
- (fileId.charCodeAt(3) << 24)) >>> 0;
202
- }
203
- function withTransaction(db, beginSql, operation) {
204
- let started = false;
205
- try {
206
- db.exec(beginSql);
207
- started = true;
208
- const result = operation();
209
- db.exec('COMMIT');
210
- started = false;
211
- return result;
212
- }
213
- catch (error) {
214
- if (started) {
215
- db.exec('ROLLBACK');
216
- }
217
- throw error;
218
- }
219
- }
220
- function readPragmaText(db, name) {
221
- const statement = db.prepare(`PRAGMA ${name}`);
222
- const row = statement.get();
223
- const value = row ? Object.values(row)[0] : undefined;
224
- return typeof value === 'string' ? value.toLowerCase() : String(value ?? '').toLowerCase();
225
- }
226
- function applyPerformanceProfile(db, profile) {
227
- db.exec(`PRAGMA page_size = ${DEFAULT_PAGE_SIZE}`);
228
- db.exec(`PRAGMA threads = ${DEFAULT_THREAD_COUNT}`);
229
- db.exec(`PRAGMA mmap_size = ${DEFAULT_MMAP_SIZE}`);
230
- if (profile === 'safe') {
231
- db.exec('PRAGMA journal_mode = DELETE');
232
- db.exec('PRAGMA synchronous = FULL');
233
- return 'BEGIN IMMEDIATE';
234
- }
235
- db.exec('PRAGMA journal_mode = OFF');
236
- if (readPragmaText(db, 'journal_mode') !== 'off') {
237
- db.exec('PRAGMA journal_mode = MEMORY');
238
- }
239
- db.exec('PRAGMA synchronous = OFF');
240
- db.exec('PRAGMA locking_mode = EXCLUSIVE');
241
- db.exec('PRAGMA temp_store = MEMORY');
242
- db.exec(`PRAGMA cache_size = ${DEFAULT_CACHE_SIZE}`);
243
- return 'BEGIN EXCLUSIVE';
244
- }
245
- function createArgumentAppender(fieldCount) {
246
- switch (fieldCount) {
247
- case 1:
248
- return (pendingArgs, fieldValues, recordOffset, recordLength, sequence) => pendingArgs.push(fieldValues[0], recordOffset, recordLength, sequence);
249
- case 2:
250
- return (pendingArgs, fieldValues, recordOffset, recordLength, sequence) => pendingArgs.push(fieldValues[0], fieldValues[1], recordOffset, recordLength, sequence);
251
- case 3:
252
- return (pendingArgs, fieldValues, recordOffset, recordLength, sequence) => pendingArgs.push(fieldValues[0], fieldValues[1], fieldValues[2], recordOffset, recordLength, sequence);
253
- case 4:
254
- return (pendingArgs, fieldValues, recordOffset, recordLength, sequence) => pendingArgs.push(fieldValues[0], fieldValues[1], fieldValues[2], fieldValues[3], recordOffset, recordLength, sequence);
255
- default:
256
- return (pendingArgs, fieldValues, recordOffset, recordLength, sequence) => {
257
- for (let index = 0; index < fieldValues.length; index += 1) {
258
- pendingArgs.push(fieldValues[index]);
259
- }
260
- pendingArgs.push(recordOffset, recordLength, sequence);
261
- };
262
- }
263
- }
264
- function buildInsertSql(recordTableName, fieldNames, rowCount) {
265
- const columnNames = [...fieldNames.map((fieldName) => `"${fieldName}"`), 'data_offset', 'data_length', 'sequence'].join(', ');
266
- const valueTuple = `(${Array.from({ length: fieldNames.length + 3 }, () => '?').join(', ')})`;
267
- return `INSERT INTO "${recordTableName}" (${columnNames}) VALUES ${Array.from({ length: rowCount }, () => valueTuple).join(', ')}`;
268
- }
269
- function createBatchedRowWriter(db, recordTableName, fieldNames) {
270
- const fullStatement = db.prepare(buildInsertSql(recordTableName, fieldNames, INSERT_BATCH_SIZE));
271
- const partialStatements = new Map();
272
- const appendArgs = createArgumentAppender(fieldNames.length);
273
- let pendingArgs = [];
274
- let pendingRowCount = 0;
275
- function statementFor(rowCount) {
276
- if (rowCount === INSERT_BATCH_SIZE) {
277
- return fullStatement;
278
- }
279
- let statement = partialStatements.get(rowCount);
280
- if (!statement) {
281
- statement = db.prepare(buildInsertSql(recordTableName, fieldNames, rowCount));
282
- partialStatements.set(rowCount, statement);
283
- }
284
- return statement;
285
- }
286
- function flushRows() {
287
- if (pendingRowCount === 0) {
288
- return;
289
- }
290
- statementFor(pendingRowCount).run(...pendingArgs);
291
- pendingArgs = [];
292
- pendingRowCount = 0;
293
- }
294
- return {
295
- writeRow(fieldValues, recordOffset, recordLength, sequence) {
296
- appendArgs(pendingArgs, fieldValues, recordOffset, recordLength, sequence);
297
- pendingRowCount += 1;
298
- if (pendingRowCount === INSERT_BATCH_SIZE) {
299
- flushRows();
300
- }
301
- },
302
- writeCompiledRow(appendFieldValues, data, recordOffset, recordLength, sequence) {
303
- appendFieldValues(pendingArgs, data);
304
- pendingArgs.push(recordOffset, recordLength, sequence);
305
- pendingRowCount += 1;
306
- if (pendingRowCount === INSERT_BATCH_SIZE) {
307
- flushRows();
308
- }
309
- },
310
- flushRows,
311
- };
312
- }
313
- export class FlatSQLArtifactBuilder {
314
- schema;
315
- db;
316
- beginTransactionSql;
317
- tableByName = new Map();
318
- fileIdToTable = new Map();
319
- extractors = new Map();
320
- queryCache = new Map();
321
- queryResultCache = new Map();
322
- sequence = 1;
323
- static fromSchema(source, options) {
324
- const schemaName = options.name ?? 'artifact';
325
- const cacheKey = `${schemaName}\u0000${source}`;
326
- let schema = schemaCache.get(cacheKey);
327
- if (!schema) {
328
- schema = parseSchema(source, schemaName);
329
- schemaCache.set(cacheKey, schema);
330
- }
331
- return new FlatSQLArtifactBuilder(schema, options);
332
- }
333
- constructor(schema, options) {
334
- if (!options.sqlitePath) {
335
- throw new Error('Artifact builder requires sqlitePath');
336
- }
337
- this.schema = schema;
338
- this.db = new DatabaseSync(options.sqlitePath);
339
- this.beginTransactionSql = applyPerformanceProfile(this.db, options.performanceProfile ?? 'fast');
340
- for (const table of schema.tables) {
341
- this.tableByName.set(table.name, table);
342
- }
343
- this.createIndexTables();
344
- }
345
- registerFileId(fileId, tableName) {
346
- this.fileIdToTable.set(encodeFileId(fileId), tableName);
347
- }
348
- setFieldExtractor(tableName, extractor) {
349
- this.extractors.set(tableName, extractor);
350
- }
351
- enableDemoExtractors() {
352
- for (const [tableName, extractor] of Object.entries(demoExtractors)) {
353
- this.extractors.set(tableName, extractor);
354
- }
355
- }
356
- ingestBuffers(buffers, options = {}) {
357
- let currentOffset = options.startOffset ?? 0;
358
- const plan = this.createIngestPlan();
359
- this.queryCache.clear();
360
- this.queryResultCache.clear();
361
- withTransaction(this.db, this.beginTransactionSql, () => {
362
- for (let index = 0; index < buffers.length; index++) {
363
- const buffer = buffers[index];
364
- const fileIdCode = readFileIdCode(buffer);
365
- const tableName = this.fileIdToTable.get(fileIdCode);
366
- if (!tableName) {
367
- const fileId = readFileId(buffer);
368
- throw new Error(`No table registered for file identifier ${fileId}`);
369
- }
370
- const tablePlan = plan.tablePlans.get(tableName);
371
- if (!tablePlan) {
372
- throw new Error(`Table ${tableName} is not present in the parsed schema`);
373
- }
374
- if (!plan.touchedTables.has(tableName)) {
375
- this.dropCoveringIndexes(tablePlan.table);
376
- plan.touchedTables.add(tableName);
377
- }
378
- const recordOffset = options.offsets?.[index] ?? currentOffset;
379
- tablePlan.writeRecord(buffer, recordOffset, buffer.length, this.sequence);
380
- this.sequence += 1;
381
- currentOffset = recordOffset + buffer.length;
382
- }
383
- for (const tableName of plan.touchedTables) {
384
- const tablePlan = plan.tablePlans.get(tableName);
385
- if (tablePlan) {
386
- tablePlan.flushRows();
387
- this.createCoveringIndexes(tablePlan.table);
388
- }
389
- }
390
- });
391
- return { recordCount: buffers.length };
392
- }
393
- query(sql, params) {
394
- const resultCacheKey = buildResultCacheKey(sql, params);
395
- const cachedResult = resultCacheKey ? getCachedResult(this.queryResultCache, resultCacheKey) : undefined;
396
- if (cachedResult) {
397
- return cloneQueryResult(cachedResult);
398
- }
399
- const cacheable = isCacheableQuerySql(sql);
400
- let cached = cacheable ? getCachedStatement(this.queryCache, sql) : undefined;
401
- if (!cached) {
402
- const statement = this.db.prepare(sql);
403
- const arrayMode = typeof statement.setReturnArrays === 'function';
404
- if (arrayMode) {
405
- statement.setReturnArrays(true);
406
- }
407
- const columns = statement.columns().map((column) => column.name);
408
- cached = { statement, columns, arrayMode };
409
- if (cacheable) {
410
- setCachedStatement(this.queryCache, sql, cached);
411
- }
412
- }
413
- const rawRows = executeStatementAll(cached.statement, params);
414
- const rows = cached.arrayMode
415
- ? rawRows
416
- : rawRows.map((row) => normalizeRow(row, cached.columns));
417
- const result = {
418
- columns: [...cached.columns],
419
- rows,
420
- rowCount: rows.length,
421
- };
422
- if (resultCacheKey) {
423
- setCachedResult(this.queryResultCache, resultCacheKey, result);
424
- }
425
- return resultCacheKey ? cloneQueryResult(result) : result;
426
- }
427
- queryMany(queries) {
428
- return queries.map(({ sql, params }) => this.query(sql, params));
429
- }
430
- close() {
431
- this.queryCache.clear();
432
- this.queryResultCache.clear();
433
- this.db.close();
434
- }
435
- createIndexTables() {
436
- for (const table of this.schema.tables) {
437
- const indexedColumns = table.columns.filter((column) => column.isIndexed && !column.name.startsWith('_'));
438
- if (indexedColumns.length === 0) {
439
- continue;
440
- }
441
- const columnDefinitions = indexedColumns
442
- .map((column) => `"${column.name}" ${toSqliteType(column)} NOT NULL`)
443
- .join(',\n ');
444
- this.db.exec(`CREATE TABLE IF NOT EXISTS "${this.recordTableName(table.name)}" (
445
- ${columnDefinitions},
446
- data_offset INTEGER NOT NULL,
447
- data_length INTEGER NOT NULL,
448
- sequence INTEGER NOT NULL
449
- )`);
450
- for (const column of indexedColumns) {
451
- this.db.exec(`CREATE VIEW IF NOT EXISTS "${this.indexTableName(table.name, column.name)}" AS
452
- SELECT "${column.name}" AS key, data_offset, data_length, sequence
453
- FROM "${this.recordTableName(table.name)}"`);
454
- }
455
- }
456
- }
457
- indexTableName(tableName, columnName) {
458
- return `_idx_${tableName}_${columnName}`;
459
- }
460
- recordTableName(tableName) {
461
- return `_rows_${tableName}`;
462
- }
463
- coveringIndexName(tableName, columnName) {
464
- return `_cov_${tableName}_${columnName}`;
465
- }
466
- createCoveringIndexes(table) {
467
- for (const column of table.columns) {
468
- if (!column.isIndexed || column.name.startsWith('_')) {
469
- continue;
470
- }
471
- this.db.exec(`CREATE INDEX "${this.coveringIndexName(table.name, column.name)}"
472
- ON "${this.recordTableName(table.name)}" ("${column.name}", sequence, data_offset, data_length)`);
473
- }
474
- }
475
- dropCoveringIndexes(table) {
476
- for (const column of table.columns) {
477
- if (!column.isIndexed || column.name.startsWith('_')) {
478
- continue;
479
- }
480
- this.db.exec(`DROP INDEX IF EXISTS "${this.coveringIndexName(table.name, column.name)}"`);
481
- }
482
- }
483
- createIngestPlan() {
484
- const tablePlans = new Map();
485
- for (const [tableName, table] of this.tableByName.entries()) {
486
- const extractor = this.extractors.get(tableName);
487
- if (!extractor) {
488
- continue;
489
- }
490
- const fieldNames = table.columns
491
- .filter((column) => column.isIndexed && !column.name.startsWith('_'))
492
- .map((column) => column.name);
493
- if (fieldNames.length === 0) {
494
- continue;
495
- }
496
- const recordTableName = this.recordTableName(table.name);
497
- const rowWriter = createBatchedRowWriter(this.db, recordTableName, fieldNames);
498
- const compiledAppender = createArtifactFieldAppender(extractor, fieldNames);
499
- const extractRowValues = compiledAppender ? null : createArtifactFieldValueReader(extractor, fieldNames);
500
- tablePlans.set(tableName, {
501
- table,
502
- writeRecord: compiledAppender
503
- ? (data, recordOffset, recordLength, sequence) => rowWriter.writeCompiledRow(compiledAppender, data, recordOffset, recordLength, sequence)
504
- : (data, recordOffset, recordLength, sequence) => rowWriter.writeRow(extractRowValues(data), recordOffset, recordLength, sequence),
505
- flushRows: rowWriter.flushRows,
506
- });
507
- }
508
- return {
509
- tablePlans,
510
- touchedTables: new Set(),
511
- };
512
- }
513
- }
514
- //# sourceMappingURL=builder.js.map