@jarenjs/db 0.56.0 → 0.67.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.
- package/ARCHITECTURE.md +412 -56
- package/README.md +600 -57
- package/docs/HOSTS.md +269 -0
- package/docs/JOBS-FORMAT.md +293 -45
- package/docs/LIVE-FORMAT.md +169 -20
- package/docs/MIGRATION-FORMAT.md +142 -17
- package/docs/MODEL-FORMAT.md +752 -64
- package/docs/REPLICATION-FORMAT.md +208 -0
- package/package.json +21 -7
- package/schemas/jaren-model.draft-07.schema.json +224 -162
- package/schemas/jaren-model.schema.json +224 -162
- package/schemas/jaren-replication-snapshot.draft-07.schema.json +83 -0
- package/schemas/jaren-replication-snapshot.schema.json +83 -0
- package/schemas/jaren-replication.draft-07.schema.json +82 -0
- package/schemas/jaren-replication.schema.json +82 -0
- package/src/algebra.js +227 -9
- package/src/backup.js +161 -0
- package/src/cancellation.js +48 -0
- package/src/capture.js +230 -47
- package/src/cli.js +165 -59
- package/src/cursor.js +417 -0
- package/src/dag-job.js +154 -21
- package/src/ddl.js +102 -8
- package/src/dialect.js +268 -113
- 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 +244 -11
- package/src/document-files.js +311 -0
- package/src/document-steps.js +422 -0
- package/src/documents.js +335 -0
- package/src/driver.js +448 -61
- 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 +17 -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.js +339 -48
- package/src/entity.js +20 -22
- package/src/errors.js +430 -19
- package/src/expression.js +284 -0
- package/src/graph.js +64 -8
- package/src/index.js +48 -17
- package/src/introspect.js +583 -0
- package/src/jobs.js +843 -107
- package/src/json-bytes.js +58 -0
- package/src/live-join.js +250 -0
- package/src/live-nested.js +120 -0
- package/src/live.js +18 -4
- package/src/logical-rows.js +90 -0
- package/src/maintenance.js +175 -0
- package/src/migrate.js +248 -181
- package/src/model.js +68 -0
- package/src/plan.js +1119 -138
- package/src/pragmas.js +314 -0
- package/src/profile.js +151 -3
- package/src/query.js +1634 -323
- package/src/replication-format.js +115 -0
- package/src/replication.js +332 -0
- package/src/residual.js +17 -0
- package/src/series.js +12 -4
- package/src/store.js +1567 -273
- package/src/tracker.js +203 -29
- package/src/udf.js +88 -7
- package/types/index.d.ts +1158 -27
- package/types/node-pool.d.ts +28 -0
- package/types/node-worker.d.ts +54 -0
- package/types/node.d.ts +69 -2
- package/types/postgres.d.ts +46 -0
- package/types/typed.d.ts +27 -4
- package/types/wasm.d.ts +14 -0
package/src/ddl.js
CHANGED
|
@@ -18,6 +18,7 @@ import { analyzeQuery } from '@jarenjs/json/query';
|
|
|
18
18
|
import { DbCompileError } from './errors.js';
|
|
19
19
|
import { chain } from './driver.js';
|
|
20
20
|
import { BBOX_COMPONENTS, BBOX_INDEX_ORDER, derivedMappingFor } from './derive.js';
|
|
21
|
+
import { expressionSql, expressionStem, expressionFunctions } from './expression.js';
|
|
21
22
|
|
|
22
23
|
/** The fixed physical column names of the 0.1 mapping. */
|
|
23
24
|
export const KEY_COLUMN = 'key';
|
|
@@ -138,6 +139,39 @@ export function schemaTypeAt(schema, segments) {
|
|
|
138
139
|
/** The scalar schema types a derived index cannot be declared over. */
|
|
139
140
|
const SCALAR_TYPES = new Set(['string', 'integer', 'number', 'boolean']);
|
|
140
141
|
|
|
142
|
+
/**
|
|
143
|
+
* The comparison KIND a declared schema type implies — what a column
|
|
144
|
+
* over that member holds, and therefore how its expression has to read
|
|
145
|
+
* the member out of the document. On a dynamically typed engine the
|
|
146
|
+
* kind changes nothing; on one whose columns carry a real SQL type it
|
|
147
|
+
* is the difference between a `text` column and a type error.
|
|
148
|
+
* @param {string | undefined} schemaType
|
|
149
|
+
* @returns {'text' | 'number' | 'boolean' | undefined}
|
|
150
|
+
*/
|
|
151
|
+
export function columnKindFor(schemaType) {
|
|
152
|
+
switch (schemaType) {
|
|
153
|
+
case 'string': return 'text';
|
|
154
|
+
case 'integer': case 'number': return 'number';
|
|
155
|
+
case 'boolean': return 'boolean';
|
|
156
|
+
default: return undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The shape row for the identity column a dialect adds to every table
|
|
162
|
+
* it creates, or none. It is an ORDINARY column as far as the catalog
|
|
163
|
+
* is concerned — the engine fills it, but nothing generates it from
|
|
164
|
+
* another column — so the drift check sees it exactly as it sees the
|
|
165
|
+
* key and the document.
|
|
166
|
+
* @param {any} dialect
|
|
167
|
+
* @returns {{ name: string, type: string, generated: boolean }[]}
|
|
168
|
+
*/
|
|
169
|
+
function identityColumnExpected(dialect) {
|
|
170
|
+
return dialect.identityColumn === undefined
|
|
171
|
+
? []
|
|
172
|
+
: [{ name: dialect.identityColumn.name, type: dialect.identityColumn.type, generated: false }];
|
|
173
|
+
}
|
|
174
|
+
|
|
141
175
|
/**
|
|
142
176
|
* Whether a schema node types its value as `array` and nothing else —
|
|
143
177
|
* `type: 'array'` or `type: ['array']`. A vector column over a member
|
|
@@ -402,7 +436,10 @@ export function planCollection(name, collection, dialect, options = undefined) {
|
|
|
402
436
|
const mapping = options?.derived === 'stored' ? 'stored' : 'virtual';
|
|
403
437
|
const rtreeCapable = options?.rtree !== false;
|
|
404
438
|
const keyType = collection.identity === 'integer'
|
|
405
|
-
|
|
439
|
+
// a DATABASE-allocated key: on an engine whose auto-allocation is a
|
|
440
|
+
// column property rather than a consequence of the integer type,
|
|
441
|
+
// that property IS the declared type
|
|
442
|
+
? (dialect.autoKeyType ?? dialect.typeFor('integer', 'key'))
|
|
406
443
|
: collection.identity === 'uuid'
|
|
407
444
|
? dialect.typeFor('string', 'key')
|
|
408
445
|
: dialect.typeFor(
|
|
@@ -422,8 +459,45 @@ export function planCollection(name, collection, dialect, options = undefined) {
|
|
|
422
459
|
const virtualTables = [];
|
|
423
460
|
/** @type {Map<string, string>} */
|
|
424
461
|
const physicalByKey = new Map();
|
|
462
|
+
/** @type {{ column: string, canonical: string, functions: string[] }[]} */
|
|
463
|
+
const expressions = [];
|
|
425
464
|
|
|
426
465
|
for (const index of collection.indexes) {
|
|
466
|
+
// an EXPRESSION index: one column over the declared computation,
|
|
467
|
+
// shared by every index that declares the same canonical expression
|
|
468
|
+
if (index.expression !== undefined) {
|
|
469
|
+
const known = columnByCanonical.has(index.canonical);
|
|
470
|
+
const columnName = generatedColumnName(expressionStem(index.expression),
|
|
471
|
+
columnByCanonical, taken, { key: index.canonical, suffix: 'x' });
|
|
472
|
+
if (!known) {
|
|
473
|
+
const sql = expressionSql(index.expression, dialect, {
|
|
474
|
+
docColumnSql: dialect.quoteIdentifier(DOC_COLUMN),
|
|
475
|
+
declarations: options?.expressions ?? {},
|
|
476
|
+
registered: options?.registered !== false,
|
|
477
|
+
docPath: `${index.docPath}/expression`,
|
|
478
|
+
segmentsOf: (member) =>
|
|
479
|
+
compileIndexPath(member, `${index.docPath}/expression`).segments,
|
|
480
|
+
});
|
|
481
|
+
generated.push({
|
|
482
|
+
name: columnName,
|
|
483
|
+
// an expression's value is TEXT: one declared function, one
|
|
484
|
+
// spelling of its answer, on every engine that computes it
|
|
485
|
+
type: dialect.typeFor('string', 'generated'),
|
|
486
|
+
kind: 'text',
|
|
487
|
+
pathText: null,
|
|
488
|
+
expression: sql,
|
|
489
|
+
canonical: index.canonical,
|
|
490
|
+
});
|
|
491
|
+
expressions.push({ column: columnName, canonical: index.canonical,
|
|
492
|
+
functions: expressionFunctions(index.expression) });
|
|
493
|
+
}
|
|
494
|
+
indexes.push({
|
|
495
|
+
name: `${name}_${index.name}`,
|
|
496
|
+
unique: index.unique,
|
|
497
|
+
columns: [columnName],
|
|
498
|
+
});
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
427
501
|
const columns = [];
|
|
428
502
|
let noBtree = false;
|
|
429
503
|
for (let i = 0; i < index.paths.length; i++) {
|
|
@@ -448,9 +522,11 @@ export function planCollection(name, collection, dialect, options = undefined) {
|
|
|
448
522
|
const known = columnByCanonical.has(canonical);
|
|
449
523
|
const columnName = generatedColumnName(canonical, columnByCanonical, taken);
|
|
450
524
|
if (!known) {
|
|
525
|
+
const schemaType = schemaTypeAt(collection.schema, segments);
|
|
451
526
|
generated.push({
|
|
452
527
|
name: columnName,
|
|
453
|
-
type: dialect.typeFor(
|
|
528
|
+
type: dialect.typeFor(schemaType, 'generated'),
|
|
529
|
+
kind: columnKindFor(schemaType),
|
|
454
530
|
pathText,
|
|
455
531
|
canonical,
|
|
456
532
|
});
|
|
@@ -511,6 +587,10 @@ export function planCollection(name, collection, dialect, options = undefined) {
|
|
|
511
587
|
keyType,
|
|
512
588
|
generated,
|
|
513
589
|
derived,
|
|
590
|
+
/** The declared-expression columns, with the functions each calls:
|
|
591
|
+
* what a store registers before it can so much as SELECT from the
|
|
592
|
+
* table it created. */
|
|
593
|
+
expressions,
|
|
514
594
|
columnByCanonical,
|
|
515
595
|
virtualTables,
|
|
516
596
|
createSql,
|
|
@@ -518,6 +598,7 @@ export function planCollection(name, collection, dialect, options = undefined) {
|
|
|
518
598
|
columns: [
|
|
519
599
|
{ name: KEY_COLUMN, type: keyType, generated: false },
|
|
520
600
|
{ name: DOC_COLUMN, type: dialect.docColumnType, generated: false },
|
|
601
|
+
...identityColumnExpected(dialect),
|
|
521
602
|
// a STORED derived column is an ordinary one: the flag is what
|
|
522
603
|
// `pragma_table_xinfo` reports, and it is the difference a file
|
|
523
604
|
// moved between the two physical mappings shows up as
|
|
@@ -639,6 +720,13 @@ export function comparableDeclaredSql(sql) {
|
|
|
639
720
|
*/
|
|
640
721
|
function verifyDeclaredSql(connection, plan, disagree) {
|
|
641
722
|
const dialect = connection.dialect;
|
|
723
|
+
// An engine that does not keep each object's CREATE text has nothing
|
|
724
|
+
// to compare: the structural check above (columns, their types and
|
|
725
|
+
// generatedness, the indexes and their covered columns in order, and
|
|
726
|
+
// for an entity its foreign-key tuples) is the whole of the drift
|
|
727
|
+
// check there, and `capabilities.declaredSqlText` is what says so
|
|
728
|
+
// rather than a silent pass.
|
|
729
|
+
if (dialect.capabilities.declaredSqlText !== true) return null;
|
|
642
730
|
const planned = new Map();
|
|
643
731
|
// an R*Tree virtual table is NOT owned by the collection table —
|
|
644
732
|
// `declaredSql` is scoped to `tbl_name`, and a virtual table's is
|
|
@@ -716,15 +804,19 @@ export function verifyShape(connection, plan, collection, docPath) {
|
|
|
716
804
|
};
|
|
717
805
|
return chain(connection.prepare(dialect.introspect.columns(plan.table)), (columnsStatement) =>
|
|
718
806
|
chain(columnsStatement.all([]), (columnRows) => {
|
|
807
|
+
// both sides through the dialect's own reduction, so a declared
|
|
808
|
+
// type the catalog reports differently (an auto-key's allocation
|
|
809
|
+
// clause, a width the engine normalizes) compares as itself
|
|
810
|
+
const comparableType = dialect.comparableColumnType;
|
|
719
811
|
const actual = columnRows
|
|
720
812
|
.map((row) => ({
|
|
721
813
|
name: String(row.name),
|
|
722
|
-
type: String(row.type)
|
|
814
|
+
type: comparableType(String(row.type)),
|
|
723
815
|
generated: Number(row.hidden) !== 0,
|
|
724
816
|
}))
|
|
725
817
|
.sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
726
818
|
const expected = [...plan.expected.columns]
|
|
727
|
-
.map((c) => ({ ...c, type: c.type
|
|
819
|
+
.map((c) => ({ ...c, type: comparableType(c.type) }))
|
|
728
820
|
.sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
729
821
|
if (actual.length !== expected.length) {
|
|
730
822
|
// name what is missing or extra: a count alone sends the reader
|
|
@@ -887,8 +979,9 @@ export function planEntity(name, entityMapping, entities, dialect) {
|
|
|
887
979
|
targetColumn: column.references.column ?? null,
|
|
888
980
|
})),
|
|
889
981
|
expected: {
|
|
890
|
-
columns: columns
|
|
891
|
-
.map((column) => ({ name: column.name, type: column.type, generated: false }))
|
|
982
|
+
columns: [...columns
|
|
983
|
+
.map((column) => ({ name: column.name, type: column.type, generated: false })),
|
|
984
|
+
...identityColumnExpected(dialect)]
|
|
892
985
|
.sort((a, b) => (a.name < b.name ? -1 : 1)),
|
|
893
986
|
indexes: expectedIndexes.sort((a, b) => (a.name < b.name ? -1 : 1)),
|
|
894
987
|
},
|
|
@@ -936,8 +1029,9 @@ export function planJoinTable(tableName, join, entities, dialect) {
|
|
|
936
1029
|
targetColumn: column.references.column ?? null,
|
|
937
1030
|
})),
|
|
938
1031
|
expected: {
|
|
939
|
-
columns: columns
|
|
940
|
-
.map((column) => ({ name: column.name, type: column.type, generated: false }))
|
|
1032
|
+
columns: [...columns
|
|
1033
|
+
.map((column) => ({ name: column.name, type: column.type, generated: false })),
|
|
1034
|
+
...identityColumnExpected(dialect)]
|
|
941
1035
|
.sort((a, b) => (a.name < b.name ? -1 : 1)),
|
|
942
1036
|
indexes: [],
|
|
943
1037
|
},
|