@jarenjs/db 0.49.2 → 0.66.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/ARCHITECTURE.md +420 -71
- package/README.md +711 -79
- package/docs/HOSTS.md +269 -0
- package/docs/JOBS-FORMAT.md +309 -45
- package/docs/LIVE-FORMAT.md +156 -19
- package/docs/MIGRATION-FORMAT.md +247 -40
- package/docs/MODEL-FORMAT.md +968 -86
- package/package.json +21 -8
- package/schemas/jaren-migration.draft-07.schema.json +73 -0
- package/schemas/jaren-migration.schema.json +73 -0
- package/schemas/jaren-model.draft-07.schema.json +224 -162
- package/schemas/jaren-model.schema.json +224 -162
- package/src/algebra.js +227 -9
- package/src/backup.js +161 -0
- package/src/cancellation.js +48 -0
- package/src/capture.js +255 -44
- package/src/cli.js +337 -50
- package/src/cursor.js +411 -0
- package/src/dag-job.js +154 -21
- package/src/ddl.js +125 -11
- package/src/dialect.js +267 -112
- package/src/dialects/expression-read.js +158 -0
- package/src/dialects/postgres.js +618 -0
- package/src/dialects/rtree-ddl.js +129 -0
- package/src/dialects/sqlite.js +245 -12
- package/src/document-files.js +311 -0
- package/src/document-steps.js +422 -0
- package/src/documents.js +335 -0
- package/src/driver.js +503 -69
- package/src/drivers/bun.js +37 -1
- package/src/drivers/indexeddb-snapshot.js +149 -0
- package/src/drivers/node-pool.js +11 -0
- package/src/drivers/node-worker-endpoint.js +105 -0
- package/src/drivers/node-worker.js +204 -0
- package/src/drivers/node.js +41 -7
- package/src/drivers/postgres.js +331 -0
- package/src/drivers/wasm-oo1.js +97 -0
- package/src/drivers/wasm-session.js +67 -0
- package/src/drivers/wasm.js +18 -83
- package/src/drivers/worker-pool.js +183 -0
- package/src/drivers/worker-protocol.js +79 -0
- package/src/drivers/worker-queue.js +60 -0
- package/src/emit-model.js +14 -0
- package/src/emit.js +349 -51
- package/src/entity.js +102 -59
- package/src/errors.js +430 -2
- package/src/expression.js +284 -0
- package/src/graph.js +64 -8
- package/src/index.js +48 -19
- package/src/introspect.js +583 -0
- package/src/jobs.js +870 -99
- package/src/json-bytes.js +58 -0
- package/src/live-time.js +12 -3
- package/src/live.js +11 -1
- package/src/maintenance.js +175 -0
- package/src/migrate.js +606 -333
- package/src/model.js +241 -8
- package/src/plan.js +1238 -160
- package/src/pragmas.js +314 -0
- package/src/profile.js +151 -3
- package/src/query.js +1748 -312
- package/src/residual.js +17 -0
- package/src/series.js +12 -4
- package/src/store.js +1672 -276
- package/src/tracker.js +367 -68
- package/src/udf.js +88 -7
- package/types/index.d.ts +1246 -32
- package/types/node-pool.d.ts +28 -0
- package/types/node-worker.d.ts +54 -0
- package/types/node.d.ts +72 -3
- package/types/postgres.d.ts +46 -0
- package/types/typed.d.ts +81 -3
- package/types/wasm.d.ts +21 -0
- package/dist/types/algebra.d.ts +0 -230
- package/dist/types/app.d.ts +0 -49
- package/dist/types/capture.d.ts +0 -85
- package/dist/types/cli.d.ts +0 -2
- package/dist/types/dag-job.d.ts +0 -40
- package/dist/types/ddl.d.ts +0 -229
- package/dist/types/derive.d.ts +0 -250
- package/dist/types/dialect.d.ts +0 -154
- package/dist/types/dialects/sqlite.d.ts +0 -9
- package/dist/types/driver.d.ts +0 -110
- package/dist/types/drivers/bun.d.ts +0 -47
- package/dist/types/drivers/node.d.ts +0 -37
- package/dist/types/drivers/wasm.d.ts +0 -65
- package/dist/types/emit-model.d.ts +0 -44
- package/dist/types/emit.d.ts +0 -75
- package/dist/types/entity.d.ts +0 -23
- package/dist/types/errors.d.ts +0 -170
- package/dist/types/graph.d.ts +0 -28
- package/dist/types/index.d.ts +0 -37
- package/dist/types/jobs.d.ts +0 -140
- package/dist/types/knn.d.ts +0 -69
- package/dist/types/live-time.d.ts +0 -141
- package/dist/types/live.d.ts +0 -64
- package/dist/types/migrate.d.ts +0 -170
- package/dist/types/model.d.ts +0 -36
- package/dist/types/patch-sql.d.ts +0 -37
- package/dist/types/plan.d.ts +0 -142
- package/dist/types/profile.d.ts +0 -80
- package/dist/types/query.d.ts +0 -112
- package/dist/types/residual.d.ts +0 -64
- package/dist/types/series.d.ts +0 -227
- package/dist/types/store.d.ts +0 -60
- package/dist/types/tracker.d.ts +0 -43
- package/dist/types/typed.d.ts +0 -15
- package/dist/types/types.d.ts +0 -26
- package/dist/types/udf.d.ts +0 -75
- package/dist/types/window.d.ts +0 -52
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/** JSON byte accounting over decoded data, without allocating encoded text. */
|
|
3
|
+
|
|
4
|
+
/** UTF-8 bytes of a JSON string, including quotes and well-formed escapes.
|
|
5
|
+
* @param {string} value @returns {number}
|
|
6
|
+
*/
|
|
7
|
+
export function jsonStringBytes(value) {
|
|
8
|
+
let bytes = 2;
|
|
9
|
+
for (let i = 0; i < value.length; i++) {
|
|
10
|
+
const code = value.charCodeAt(i);
|
|
11
|
+
if (code === 34 || code === 92 || code === 8 || code === 9
|
|
12
|
+
|| code === 10 || code === 12 || code === 13) bytes += 2;
|
|
13
|
+
else if (code < 32) bytes += 6;
|
|
14
|
+
else if (code < 128) bytes++;
|
|
15
|
+
else if (code < 2048) bytes += 2;
|
|
16
|
+
else if (code >= 0xd800 && code <= 0xdfff) {
|
|
17
|
+
const next = value.charCodeAt(i + 1);
|
|
18
|
+
if (code <= 0xdbff && next >= 0xdc00 && next <= 0xdfff) { bytes += 4; i++; }
|
|
19
|
+
else bytes += 6;
|
|
20
|
+
}
|
|
21
|
+
else bytes += 3;
|
|
22
|
+
}
|
|
23
|
+
return bytes;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The JSON serialization size of decoded JSON data. Objects are memoized
|
|
27
|
+
* during decoding, so a nested include reads its size without another walk.
|
|
28
|
+
* @param {any} value @param {WeakMap<object, number>} [sizes]
|
|
29
|
+
* @returns {number}
|
|
30
|
+
*/
|
|
31
|
+
export function jsonBytes(value, sizes = new WeakMap()) {
|
|
32
|
+
if (value === null) return 4;
|
|
33
|
+
if (typeof value === 'string') return jsonStringBytes(value);
|
|
34
|
+
if (typeof value === 'boolean') return value ? 4 : 5;
|
|
35
|
+
if (typeof value === 'number') return Number.isFinite(value) ? String(value).length : 4;
|
|
36
|
+
if (typeof value !== 'object') throw new TypeError('byte accounting requires decoded JSON data');
|
|
37
|
+
const cached = sizes.get(value);
|
|
38
|
+
if (cached !== undefined) return cached;
|
|
39
|
+
const keys = Object.keys(value);
|
|
40
|
+
let bytes = 2 + Math.max(0, keys.length - 1);
|
|
41
|
+
const array = Array.isArray(value);
|
|
42
|
+
for (const key of keys) bytes += (array ? 0 : jsonStringBytes(key) + 1) + jsonBytes(value[key], sizes);
|
|
43
|
+
sizes.set(value, bytes);
|
|
44
|
+
return bytes;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Decode and account bottom-up in the decoder's construction traversal.
|
|
48
|
+
* The caller checks the incoming text bound before decoding and checks the
|
|
49
|
+
* recorded nested bounds before attaching any reconstructed children.
|
|
50
|
+
* @param {string} text @param {WeakMap<object, number>} sizes
|
|
51
|
+
* @returns {any}
|
|
52
|
+
*/
|
|
53
|
+
export function decodeCountedJson(text, sizes) {
|
|
54
|
+
return JSON.parse(text, (_key, value) => {
|
|
55
|
+
if (value !== null && typeof value === 'object') jsonBytes(value, sizes);
|
|
56
|
+
return value;
|
|
57
|
+
});
|
|
58
|
+
}
|
package/src/live-time.js
CHANGED
|
@@ -77,7 +77,7 @@ export function normalizeEventTime(options, collection) {
|
|
|
77
77
|
const declared = options?.eventTime;
|
|
78
78
|
if (declared === undefined || declared === null) return null;
|
|
79
79
|
const refuse = (reason) => {
|
|
80
|
-
throw new DbCompileError('JD0053', reason, {
|
|
80
|
+
throw new DbCompileError('JD0053', reason, `/collections/${collection}`);
|
|
81
81
|
};
|
|
82
82
|
if (!isJsonObject(declared))
|
|
83
83
|
refuse('live eventTime is an object with a path, a watermark and a retention');
|
|
@@ -178,6 +178,15 @@ export function classifyEventTime(inner, windowed, keyed, eventTime) {
|
|
|
178
178
|
return rerun(`'${name}' — a named zone resolves through the injected provider, which `
|
|
179
179
|
+ 'maintenance would have to consult per boundary');
|
|
180
180
|
}
|
|
181
|
+
// the kernel reads member NAMES (`at`, `value`); the document spells
|
|
182
|
+
// them as row selectors (`$.at`) — handed the document's spelling, the
|
|
183
|
+
// kernel read `row['$.at']` and every fold died on its first reading
|
|
184
|
+
const valueMember = spec.value === undefined ? null : singularSelector(spec.value);
|
|
185
|
+
if (spec.value !== undefined && valueMember === null) {
|
|
186
|
+
return rerun(`'${name}' — the spec reads a value selector this view cannot follow`);
|
|
187
|
+
}
|
|
188
|
+
const kernelSpec = { ...spec, at: eventTime.member,
|
|
189
|
+
...(valueMember === null ? {} : { value: valueMember }) };
|
|
181
190
|
const aggregate = spec.aggregate ?? 'mean';
|
|
182
191
|
if (!MAINTAINED_AGGREGATES.includes(aggregate)) {
|
|
183
192
|
return rerun(`'${name}' — '${aggregate}' names a row by its position in the series, `
|
|
@@ -215,7 +224,7 @@ export function classifyEventTime(inner, windowed, keyed, eventTime) {
|
|
|
215
224
|
return {
|
|
216
225
|
strategy: 'rolling',
|
|
217
226
|
source: operand.source,
|
|
218
|
-
spec,
|
|
227
|
+
spec: kernelSpec,
|
|
219
228
|
member: eventTime.member,
|
|
220
229
|
width: span.width,
|
|
221
230
|
eventTime,
|
|
@@ -236,7 +245,7 @@ export function classifyEventTime(inner, windowed, keyed, eventTime) {
|
|
|
236
245
|
return {
|
|
237
246
|
strategy: 'bucket',
|
|
238
247
|
source: operand.source,
|
|
239
|
-
spec,
|
|
248
|
+
spec: kernelSpec,
|
|
240
249
|
member: eventTime.member,
|
|
241
250
|
ladder,
|
|
242
251
|
fill,
|
package/src/live.js
CHANGED
|
@@ -79,12 +79,22 @@ function unwrapDocument(document) {
|
|
|
79
79
|
return { inner: doc, whole, windowed, offset, limit, aggregate };
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** Whether a `$for` source is the whole collection: the bare `$[*]`, or
|
|
83
|
+
* its packed spelling `["$[*]"]` — one array item a `$for` unpacks back
|
|
84
|
+
* into the rows, which is how `@jarenjs/linq` binds every source so an
|
|
85
|
+
* array-valued row stays one item. Over a collection the two are the
|
|
86
|
+
* same rows (the planner reads through the packing the same way). */
|
|
87
|
+
function isWholeCollection(source) {
|
|
88
|
+
return source === '$[*]'
|
|
89
|
+
|| (Array.isArray(source) && source.length === 1 && source[0] === '$[*]');
|
|
90
|
+
}
|
|
91
|
+
|
|
82
92
|
/** The single for-binding name of a canonical flwor, or null. */
|
|
83
93
|
function bindingNameOf(inner) {
|
|
84
94
|
if (!isJsonObject(inner) || !isJsonObject(inner.$for)) return null;
|
|
85
95
|
const names = Object.keys(inner.$for);
|
|
86
96
|
if (names.length !== 1) return null;
|
|
87
|
-
return inner.$for[names[0]]
|
|
97
|
+
return isWholeCollection(inner.$for[names[0]]) ? names[0] : null;
|
|
88
98
|
}
|
|
89
99
|
|
|
90
100
|
/** The whole-documents read behind a flwor: same binding, same
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file The maintenance surface: the four operations an operator runs
|
|
4
|
+
* on a production SQLite database — a WAL checkpoint, an integrity
|
|
5
|
+
* check, a foreign-key check and `PRAGMA optimize` — as typed store
|
|
6
|
+
* operations. Each runs under the store gate (so it never interleaves
|
|
7
|
+
* an in-flight write), returns SQLite's own answer as typed data, and
|
|
8
|
+
* is refused by code (`JD2077`) exactly where the capability report
|
|
9
|
+
* says it is unavailable: a driver whose binding does not declare it,
|
|
10
|
+
* or — for the two that write — a read-only store, on which the engine
|
|
11
|
+
* would otherwise answer a checkpoint with a silent no-op.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here interprets the engine's numbers. A checkpoint answers
|
|
14
|
+
* the row `PRAGMA wal_checkpoint` returns, so a store that is not in
|
|
15
|
+
* WAL mode reports `-1` frames as the engine does; a second passive
|
|
16
|
+
* checkpoint reports the same frame counts as the first (the frames
|
|
17
|
+
* stay in the log until a writer restarts it) and a second `truncate`
|
|
18
|
+
* reports zeros; an integrity check answers `ok: true` for the single
|
|
19
|
+
* `ok` row and the engine's problem rows verbatim otherwise — corruption
|
|
20
|
+
* is the RESULT, never a throw. Only a driver failure throws (`JD2078`).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { DbRuntimeError, wrapDriverError } from './errors.js';
|
|
24
|
+
import { chain, attempt } from './driver.js';
|
|
25
|
+
import { refuseCancelled } from './cancellation.js';
|
|
26
|
+
|
|
27
|
+
/** The checkpoint modes `PRAGMA wal_checkpoint` accepts, closed. */
|
|
28
|
+
export const CHECKPOINT_MODES = Object.freeze(['passive', 'full', 'restart', 'truncate']);
|
|
29
|
+
|
|
30
|
+
/** The operations, in the order the capability report lists them. */
|
|
31
|
+
export const MAINTENANCE_OPERATIONS = Object.freeze(
|
|
32
|
+
['checkpoint', 'integrityCheck', 'foreignKeyCheck', 'optimize']);
|
|
33
|
+
|
|
34
|
+
/** The two operations that write, refused on a read-only store. */
|
|
35
|
+
const WRITES = new Set(['checkpoint', 'optimize']);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The driver failure wrap: an error that already carries a code is the
|
|
39
|
+
* error; the driver's own failure becomes `JD2078` with the original as
|
|
40
|
+
* `cause`.
|
|
41
|
+
* @param {string} operation
|
|
42
|
+
* @returns {(error: any) => Error}
|
|
43
|
+
*/
|
|
44
|
+
const failed = (operation) => (error) => wrapDriverError(error,
|
|
45
|
+
{ code: 'JD2078', reason: `the maintenance operation '${operation}' failed`, always: true });
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The per-operation capability booleans of one store: the binding's
|
|
49
|
+
* declaration, and for the writing operations the store's read-only
|
|
50
|
+
* flag as well — the report says `false` exactly where a call is
|
|
51
|
+
* refused.
|
|
52
|
+
* @param {Readonly<Record<string, boolean>> | undefined} declared - the
|
|
53
|
+
* connection's `capabilities.maintenance`
|
|
54
|
+
* @param {boolean} readOnly
|
|
55
|
+
* @returns {Readonly<Record<string, boolean>>}
|
|
56
|
+
*/
|
|
57
|
+
export function maintenanceCapabilities(declared, readOnly) {
|
|
58
|
+
/** @type {Record<string, boolean>} */
|
|
59
|
+
const out = {};
|
|
60
|
+
for (const operation of MAINTENANCE_OPERATIONS) {
|
|
61
|
+
out[operation] = declared?.[operation] === true && !(readOnly && WRITES.has(operation));
|
|
62
|
+
}
|
|
63
|
+
return Object.freeze(out);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build the maintenance operations over a connection. The caller owns
|
|
68
|
+
* the gate: every operation here is value-or-promise and issues its
|
|
69
|
+
* statements wherever the connection routes them.
|
|
70
|
+
* Every operation takes `{ signal, deadline }` and checks them once,
|
|
71
|
+
* before the one statement it issues — the granularity the driver has
|
|
72
|
+
* (`JD2081` for an abort, `JD2075` for a passed deadline, on the clock
|
|
73
|
+
* the store was opened with).
|
|
74
|
+
* @param {{ connection: any, readOnly: boolean, now: () => number }} context
|
|
75
|
+
* @returns {{ capabilities: Readonly<Record<string, boolean>>,
|
|
76
|
+
* checkpoint: (options?: { mode?: string, signal?: AbortSignal, deadline?: number }) => any,
|
|
77
|
+
* integrityCheck: (options?: { limit?: number, signal?: AbortSignal, deadline?: number }) => any,
|
|
78
|
+
* foreignKeyCheck: (options?: { signal?: AbortSignal, deadline?: number }) => any,
|
|
79
|
+
* optimize: (options?: { signal?: AbortSignal, deadline?: number }) => any }}
|
|
80
|
+
*/
|
|
81
|
+
export function createMaintenance({ connection, readOnly, now }) {
|
|
82
|
+
const dialect = connection.dialect;
|
|
83
|
+
const capabilities = maintenanceCapabilities(connection.capabilities.maintenance, readOnly);
|
|
84
|
+
|
|
85
|
+
/** The cancellation check before an operation's statement.
|
|
86
|
+
* @param {any} options @param {string} operation */
|
|
87
|
+
const callable = (options, operation) => refuseCancelled(options, now,
|
|
88
|
+
{ abortCode: 'JD2081', aborted: `'${operation}' ran`, passed: `'${operation}' ran` });
|
|
89
|
+
|
|
90
|
+
/** @param {string} operation */
|
|
91
|
+
const require = (operation) => {
|
|
92
|
+
if (capabilities[operation] === true) return;
|
|
93
|
+
throw new DbRuntimeError('JD2077',
|
|
94
|
+
readOnly && WRITES.has(operation) && connection.capabilities.maintenance?.[operation] === true
|
|
95
|
+
? `the maintenance operation '${operation}' is unavailable on a read-only store: it writes`
|
|
96
|
+
: `the maintenance operation '${operation}' is unavailable on this store: the driver's `
|
|
97
|
+
+ 'binding does not declare it');
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** Run one pragma statement and answer its rows, driver failures wrapped.
|
|
101
|
+
* @param {string} operation @param {string} sql */
|
|
102
|
+
const rows = (operation, sql) => attempt(
|
|
103
|
+
() => chain(connection.prepare(sql), (statement) => statement.all([])),
|
|
104
|
+
failed(operation));
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
capabilities,
|
|
108
|
+
/**
|
|
109
|
+
* `PRAGMA wal_checkpoint(<mode>)`: the engine's row, typed. `busy`
|
|
110
|
+
* is whether the checkpoint could not complete because a reader or
|
|
111
|
+
* writer held it; the frame counts are the engine's (`-1` when the
|
|
112
|
+
* database is not in WAL mode).
|
|
113
|
+
* @param {{ mode?: string, signal?: AbortSignal, deadline?: number }} [options]
|
|
114
|
+
*/
|
|
115
|
+
checkpoint(options = undefined) {
|
|
116
|
+
const mode = options?.mode ?? 'passive';
|
|
117
|
+
if (typeof mode !== 'string' || !CHECKPOINT_MODES.includes(mode)) {
|
|
118
|
+
throw new TypeError(`checkpoint: mode is one of ${
|
|
119
|
+
CHECKPOINT_MODES.map((m) => `'${m}'`).join(', ')}, got ${JSON.stringify(mode)}`);
|
|
120
|
+
}
|
|
121
|
+
callable(options, 'checkpoint');
|
|
122
|
+
require('checkpoint');
|
|
123
|
+
return chain(rows('checkpoint', dialect.pragma.walCheckpoint(mode)), (answer) => {
|
|
124
|
+
const row = answer[0] ?? {};
|
|
125
|
+
return {
|
|
126
|
+
busy: Number(row.busy) === 1,
|
|
127
|
+
logFrames: Number(row.log ?? -1),
|
|
128
|
+
checkpointedFrames: Number(row.checkpointed ?? -1),
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* `PRAGMA integrity_check(<limit>)`: `ok` for the single `ok` row,
|
|
134
|
+
* otherwise the engine's problem rows verbatim. Corruption is the
|
|
135
|
+
* result, not a throw.
|
|
136
|
+
* @param {{ limit?: number, signal?: AbortSignal, deadline?: number }} [options]
|
|
137
|
+
*/
|
|
138
|
+
integrityCheck(options = undefined) {
|
|
139
|
+
const limit = options?.limit;
|
|
140
|
+
if (limit !== undefined && (!Number.isSafeInteger(limit) || limit < 1)) {
|
|
141
|
+
throw new TypeError(`integrityCheck: limit is a positive integer, got ${JSON.stringify(limit)}`);
|
|
142
|
+
}
|
|
143
|
+
callable(options, 'integrityCheck');
|
|
144
|
+
require('integrityCheck');
|
|
145
|
+
return chain(rows('integrityCheck', dialect.pragma.integrityCheck(limit)), (answer) => {
|
|
146
|
+
const problems = answer.map((row) => String(Object.values(row)[0]));
|
|
147
|
+
const ok = problems.length === 1 && problems[0] === 'ok';
|
|
148
|
+
return { ok, problems: ok ? [] : problems };
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
/** `PRAGMA foreign_key_check`: every violating row, typed.
|
|
152
|
+
* @param {{ signal?: AbortSignal, deadline?: number }} [options] */
|
|
153
|
+
foreignKeyCheck(options = undefined) {
|
|
154
|
+
callable(options, 'foreignKeyCheck');
|
|
155
|
+
require('foreignKeyCheck');
|
|
156
|
+
return chain(rows('foreignKeyCheck', dialect.pragma.foreignKeyCheck()), (answer) => ({
|
|
157
|
+
ok: answer.length === 0,
|
|
158
|
+
violations: answer.map((row) => ({
|
|
159
|
+
table: String(row.table),
|
|
160
|
+
rowId: row.rowid === null || row.rowid === undefined ? null : Number(row.rowid),
|
|
161
|
+
parent: String(row.parent),
|
|
162
|
+
fkid: Number(row.fkid),
|
|
163
|
+
})),
|
|
164
|
+
}));
|
|
165
|
+
},
|
|
166
|
+
/** `PRAGMA optimize`: the engine answers no rows, so the honest
|
|
167
|
+
* result is that it ran — no invented statistics.
|
|
168
|
+
* @param {{ signal?: AbortSignal, deadline?: number }} [options] */
|
|
169
|
+
optimize(options = undefined) {
|
|
170
|
+
callable(options, 'optimize');
|
|
171
|
+
require('optimize');
|
|
172
|
+
return chain(rows('optimize', dialect.pragma.optimize()), () => ({ ran: true }));
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|