@substrat-run/contract-tests 0.143.0 → 0.145.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/dist/attachment-refusal-suite.d.ts +3 -0
- package/dist/attachment-refusal-suite.d.ts.map +1 -0
- package/dist/attachment-refusal-suite.js +128 -0
- package/dist/attachment-refusal-suite.js.map +1 -0
- package/dist/bind-module.d.ts +220 -0
- package/dist/bind-module.d.ts.map +1 -0
- package/dist/bind-module.js +135 -0
- package/dist/bind-module.js.map +1 -0
- package/dist/binding-invite-suite.d.ts +12 -0
- package/dist/binding-invite-suite.d.ts.map +1 -0
- package/dist/binding-invite-suite.js +560 -0
- package/dist/binding-invite-suite.js.map +1 -0
- package/dist/derived-module.d.ts +414 -3
- package/dist/derived-module.d.ts.map +1 -1
- package/dist/derived-module.js +201 -6
- package/dist/derived-module.js.map +1 -1
- package/dist/derived-suite.d.ts.map +1 -1
- package/dist/derived-suite.js +351 -4
- package/dist/derived-suite.js.map +1 -1
- package/dist/entity-state-module.d.ts +16 -0
- package/dist/entity-state-module.d.ts.map +1 -1
- package/dist/entity-trash-module.d.ts +80 -0
- package/dist/entity-trash-module.d.ts.map +1 -1
- package/dist/entity-trash-module.js +73 -0
- package/dist/entity-trash-module.js.map +1 -1
- package/dist/entity-trash-suite.d.ts.map +1 -1
- package/dist/entity-trash-suite.js +94 -2
- package/dist/entity-trash-suite.js.map +1 -1
- package/dist/erasure-module.d.ts +8 -0
- package/dist/erasure-module.d.ts.map +1 -1
- package/dist/findings-suite.d.ts.map +1 -1
- package/dist/findings-suite.js +55 -1
- package/dist/findings-suite.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/modules.d.ts +248 -0
- package/dist/modules.d.ts.map +1 -1
- package/dist/modules.js +11 -1
- package/dist/modules.js.map +1 -1
- package/dist/scope-host-suite.d.ts.map +1 -1
- package/dist/scope-host-suite.js +501 -0
- package/dist/scope-host-suite.js.map +1 -1
- package/dist/shape-backfill-ties.d.ts +29 -0
- package/dist/shape-backfill-ties.d.ts.map +1 -0
- package/dist/shape-backfill-ties.js +113 -0
- package/dist/shape-backfill-ties.js.map +1 -0
- package/dist/shape-reconcile-plan.d.ts +19 -5
- package/dist/shape-reconcile-plan.d.ts.map +1 -1
- package/dist/shape-reconcile-plan.js +76 -20
- package/dist/shape-reconcile-plan.js.map +1 -1
- package/dist/stamps-module.d.ts +462 -0
- package/dist/stamps-module.d.ts.map +1 -0
- package/dist/stamps-module.js +242 -0
- package/dist/stamps-module.js.map +1 -0
- package/dist/stamps-suite.d.ts +3 -0
- package/dist/stamps-suite.d.ts.map +1 -0
- package/dist/stamps-suite.js +170 -0
- package/dist/stamps-suite.js.map +1 -0
- package/dist/vertical-events-suite.d.ts +16 -0
- package/dist/vertical-events-suite.d.ts.map +1 -1
- package/dist/vertical-events-suite.js +30 -3
- package/dist/vertical-events-suite.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type SwitchSql } from '@substrat-run/kernel';
|
|
2
|
+
interface TieCase {
|
|
3
|
+
name: string;
|
|
4
|
+
/** The pairs the backfill marked, and the ones it should have. */
|
|
5
|
+
marked: string[];
|
|
6
|
+
expected: string[];
|
|
7
|
+
/** Whether the run-once record was written once the reconcile finished. */
|
|
8
|
+
recorded: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Three cases, each its own table and entity type, each reconciled at `limit` 1 (one mark, a window
|
|
12
|
+
* of ten rows) until the reconcile finishes:
|
|
13
|
+
*
|
|
14
|
+
* - `duplicate id`: `(e1, A)`, `(e1, B)`, `(e2, C)` on an index of the id alone. The write budget
|
|
15
|
+
* ends the first pass on A, mid-id.
|
|
16
|
+
* - `null principal at the edge`: nine rows with no principal, then `(e1, NULL)` closing the first
|
|
17
|
+
* window, then `(e1, A)`. A principal-keyed seek past `(e1, NULL)` compares NULL for every e1 row.
|
|
18
|
+
* - `without rowid`: `(e1, A)`, `(e1, B)`, `(e2, C)` in a `WITHOUT ROWID` table keyed on another
|
|
19
|
+
* column, with an index on the id. Then the same table declared `WITHOUT ROWID, STRICT`, and
|
|
20
|
+
* `WITHOUT ROWID` followed by a comment: DDL text that does not end in the clause.
|
|
21
|
+
* - `a column named rowid`: the duplicate-id case in a table whose own `rowid` column, the same on
|
|
22
|
+
* every row, would stand where the row's identity should.
|
|
23
|
+
*/
|
|
24
|
+
export declare function shapeBackfillTies(raw: SwitchSql, ids: {
|
|
25
|
+
tenantId: string;
|
|
26
|
+
scopeId: string;
|
|
27
|
+
}): TieCase[];
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=shape-backfill-ties.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shape-backfill-ties.d.ts","sourceRoot":"","sources":["../src/shape-backfill-ties.ts"],"names":[],"mappings":"AAWA,OAAO,EAAkD,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtG,UAAU,OAAO;IACf,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,2EAA2E;IAC3E,QAAQ,EAAE,OAAO,CAAC;CACnB;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,EAAE,CA+EvG"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A table holder's rows that share an `idColumn` are all read, whichever row a window ends on
|
|
3
|
+
* (#2154, #2159 review). Nothing makes `idColumn` unique or the principal non-NULL, so a read keyed
|
|
4
|
+
* on the id alone, or on the id and the principal, skipped the rest of an id's rows at a window's
|
|
5
|
+
* edge, and the run-once record then closed the backfill over them for good. Pure, with no runner:
|
|
6
|
+
* each adapter hands it its scope's own SQLite as the kernel's `SwitchSql`, so the cases run on the
|
|
7
|
+
* engine it actually ships, and asserts every case marked what it expected.
|
|
8
|
+
*
|
|
9
|
+
* Harness code: it writes `_substrat_*` rows directly, which is exactly what `ctx.sql` refuses.
|
|
10
|
+
*/
|
|
11
|
+
import { entityGrantShape } from '@substrat-run/contracts';
|
|
12
|
+
import { topUpEntityGrantShapes, ulid } from '@substrat-run/kernel';
|
|
13
|
+
const KEY = 'shape-tie:use';
|
|
14
|
+
/**
|
|
15
|
+
* Three cases, each its own table and entity type, each reconciled at `limit` 1 (one mark, a window
|
|
16
|
+
* of ten rows) until the reconcile finishes:
|
|
17
|
+
*
|
|
18
|
+
* - `duplicate id`: `(e1, A)`, `(e1, B)`, `(e2, C)` on an index of the id alone. The write budget
|
|
19
|
+
* ends the first pass on A, mid-id.
|
|
20
|
+
* - `null principal at the edge`: nine rows with no principal, then `(e1, NULL)` closing the first
|
|
21
|
+
* window, then `(e1, A)`. A principal-keyed seek past `(e1, NULL)` compares NULL for every e1 row.
|
|
22
|
+
* - `without rowid`: `(e1, A)`, `(e1, B)`, `(e2, C)` in a `WITHOUT ROWID` table keyed on another
|
|
23
|
+
* column, with an index on the id. Then the same table declared `WITHOUT ROWID, STRICT`, and
|
|
24
|
+
* `WITHOUT ROWID` followed by a comment: DDL text that does not end in the clause.
|
|
25
|
+
* - `a column named rowid`: the duplicate-id case in a table whose own `rowid` column, the same on
|
|
26
|
+
* every row, would stand where the row's identity should.
|
|
27
|
+
*/
|
|
28
|
+
export function shapeBackfillTies(raw, ids) {
|
|
29
|
+
const cases = [
|
|
30
|
+
{
|
|
31
|
+
name: 'duplicate id',
|
|
32
|
+
type: 'tieseat',
|
|
33
|
+
ddl: ['CREATE TABLE shape_tie_seat (entity_id TEXT, holder TEXT)', 'CREATE INDEX shape_tie_seat_id ON shape_tie_seat (entity_id)'],
|
|
34
|
+
rows: [['e1', 'A'], ['e1', 'B'], ['e2', 'C']],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'null principal at the edge',
|
|
38
|
+
type: 'tiedesk',
|
|
39
|
+
ddl: ['CREATE TABLE shape_tie_desk (entity_id TEXT, holder TEXT)', 'CREATE INDEX shape_tie_desk_id ON shape_tie_desk (entity_id)'],
|
|
40
|
+
rows: [...Array.from({ length: 9 }, (_, i) => [`a${i}`, null]), ['e1', null], ['e1', 'A']],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'without rowid',
|
|
44
|
+
type: 'tiebadge',
|
|
45
|
+
ddl: [
|
|
46
|
+
'CREATE TABLE shape_tie_badge (k TEXT PRIMARY KEY, entity_id TEXT, holder TEXT) WITHOUT ROWID',
|
|
47
|
+
'CREATE INDEX shape_tie_badge_id ON shape_tie_badge (entity_id)',
|
|
48
|
+
],
|
|
49
|
+
rows: [['e1', 'A'], ['e1', 'B'], ['e2', 'C']],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'without rowid, strict',
|
|
53
|
+
type: 'tiestrict',
|
|
54
|
+
ddl: [
|
|
55
|
+
'CREATE TABLE shape_tie_strict (k TEXT PRIMARY KEY, entity_id TEXT, holder TEXT) WITHOUT ROWID, STRICT',
|
|
56
|
+
'CREATE INDEX shape_tie_strict_id ON shape_tie_strict (entity_id)',
|
|
57
|
+
],
|
|
58
|
+
rows: [['e1', 'A'], ['e1', 'B'], ['e2', 'C']],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'without rowid, then a comment',
|
|
62
|
+
type: 'tienote',
|
|
63
|
+
ddl: [
|
|
64
|
+
'CREATE TABLE shape_tie_note (k TEXT PRIMARY KEY, entity_id TEXT, holder TEXT) WITHOUT ROWID -- keyed on k',
|
|
65
|
+
'CREATE INDEX shape_tie_note_id ON shape_tie_note (entity_id)',
|
|
66
|
+
],
|
|
67
|
+
rows: [['e1', 'A'], ['e1', 'B'], ['e2', 'C']],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'a column named rowid',
|
|
71
|
+
type: 'tieshadow',
|
|
72
|
+
ddl: [
|
|
73
|
+
"CREATE TABLE shape_tie_shadow (rowid TEXT DEFAULT 'same', entity_id TEXT, holder TEXT)",
|
|
74
|
+
'CREATE INDEX shape_tie_shadow_id ON shape_tie_shadow (entity_id)',
|
|
75
|
+
],
|
|
76
|
+
rows: [['e1', 'A'], ['e1', 'B'], ['e2', 'C']],
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
return cases.map(({ name, type, ddl, rows }) => {
|
|
80
|
+
const table = ddl[0].split(' ')[2];
|
|
81
|
+
for (const st of ddl)
|
|
82
|
+
raw.run(st);
|
|
83
|
+
const expected = [];
|
|
84
|
+
const keyed = ddl[0].includes('WITHOUT ROWID'); // its own key column, `k`
|
|
85
|
+
rows.forEach(([id, who], i) => {
|
|
86
|
+
const holder = who && `${who}-${ulid()}`;
|
|
87
|
+
if (keyed)
|
|
88
|
+
raw.run(`INSERT INTO ${table} (k, entity_id, holder) VALUES (?, ?, ?)`, `k${i}`, id, holder);
|
|
89
|
+
else
|
|
90
|
+
raw.run(`INSERT INTO ${table} (entity_id, holder) VALUES (?, ?)`, id, holder);
|
|
91
|
+
if (!holder)
|
|
92
|
+
return;
|
|
93
|
+
raw.run('INSERT INTO _substrat_tuples (subject, relation, object) VALUES (?, ?, ?)', `principal:${holder}`, `granted:${KEY}`, `${type}:${id}`);
|
|
94
|
+
expected.push(`principal:${holder} ${type}:${id}`);
|
|
95
|
+
});
|
|
96
|
+
const shapes = [entityGrantShape.parse({ entityType: type, permissions: [KEY], bootstrap: true, holder: { table, idColumn: 'entity_id', principalColumn: 'holder' } })];
|
|
97
|
+
let after = null;
|
|
98
|
+
let passes = 0;
|
|
99
|
+
do {
|
|
100
|
+
after = topUpEntityGrantShapes(raw, { ...ids, shapes, now: new Date().toISOString(), limit: 1, after, mintEventId: () => ulid(), version: null }).next;
|
|
101
|
+
passes++;
|
|
102
|
+
} while (after && passes < 100);
|
|
103
|
+
// A cursor that never ends is a failure of its own, whatever happened to be marked by the cap.
|
|
104
|
+
if (after)
|
|
105
|
+
throw new Error(`${name}: the reconcile did not finish in ${passes} passes`);
|
|
106
|
+
const marked = raw
|
|
107
|
+
.all("SELECT subject || ' ' || object AS pair FROM _substrat_tuples WHERE relation = 'bootstrap' AND object >= ? AND object < ? ORDER BY 1", `${type}:`, `${type};`)
|
|
108
|
+
.map((r) => String(r['pair']));
|
|
109
|
+
const recorded = raw.all("SELECT 1 FROM _substrat_tuples WHERE subject = ? AND relation = 'shape-backfilled'", `shape:${type}`).length === 1;
|
|
110
|
+
return { name, marked, expected: expected.sort(), recorded };
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=shape-backfill-ties.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shape-backfill-ties.js","sourceRoot":"","sources":["../src/shape-backfill-ties.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
import { type SwitchSql } from '@substrat-run/kernel';
|
|
2
|
+
type HolderKind = 'self' | 'grantee' | 'table';
|
|
2
3
|
interface ShapeReconcilePlanReport {
|
|
3
4
|
/** The plan of each marker walk the passes sent (one per retire and top-up walk). */
|
|
4
5
|
walks: string[];
|
|
5
|
-
/** The plan of each
|
|
6
|
-
backfills: string[]
|
|
6
|
+
/** The plan of each backfill read, by the holder kind it read for. */
|
|
7
|
+
backfills: Record<HolderKind, string[]>;
|
|
7
8
|
/** Every planned table scan of `_substrat_tuples`, with the statement that planned it. */
|
|
8
9
|
scans: string[];
|
|
9
10
|
/** Passes the reconcile took, and whether every holder ended up with the whole shape. */
|
|
10
11
|
passes: number;
|
|
11
12
|
complete: boolean;
|
|
13
|
+
/** Markers the backfill wrote, and how many legacy holders there were to mark. */
|
|
14
|
+
marks: number;
|
|
15
|
+
holders: number;
|
|
16
|
+
/** The most rows any one pass's backfill reads fetched, and the read budget of a pass. */
|
|
17
|
+
maxFetched: number;
|
|
18
|
+
window: number;
|
|
19
|
+
/** The most rows the engine read in one pass, every statement included: given `rowsRead` only. */
|
|
20
|
+
maxRowsRead: number | null;
|
|
12
21
|
}
|
|
13
22
|
/**
|
|
14
|
-
* Seed
|
|
15
|
-
* retired key, and another type's markers beside them — then reconcile
|
|
16
|
-
* resuming from the last, planning every statement.
|
|
23
|
+
* Seed three shapes' worlds on `raw` — holders of the old shape, legacy holders with no marker for
|
|
24
|
+
* each holder kind, a retired key, and another type's markers beside them — then reconcile them in
|
|
25
|
+
* small passes, each resuming from the last, planning every statement. `legacy` legacy holders per
|
|
26
|
+
* kind (default 5); `rowsRead`, when the engine counts them, the running total of rows it has read.
|
|
27
|
+
* Run it on a scope nothing else is writing.
|
|
17
28
|
*/
|
|
18
29
|
export declare function shapeReconcilePlans(raw: SwitchSql, ids: {
|
|
19
30
|
tenantId: string;
|
|
20
31
|
scopeId: string;
|
|
32
|
+
}, opts?: {
|
|
33
|
+
legacy?: number;
|
|
34
|
+
rowsRead?: () => number;
|
|
21
35
|
}): ShapeReconcilePlanReport;
|
|
22
36
|
export {};
|
|
23
37
|
//# sourceMappingURL=shape-reconcile-plan.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shape-reconcile-plan.d.ts","sourceRoot":"","sources":["../src/shape-reconcile-plan.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shape-reconcile-plan.d.ts","sourceRoot":"","sources":["../src/shape-reconcile-plan.ts"],"names":[],"mappings":"AAsBA,OAAO,EAA8E,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAIlI,KAAK,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAE/C,UAAU,wBAAwB;IAChC,qFAAqF;IACrF,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;IACxC,0FAA0F;IAC1F,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,yFAAyF;IACzF,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,kGAAkG;IAClG,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAQD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,SAAS,EACd,GAAG,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAC1C,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,MAAM,CAAA;CAAO,GACtD,wBAAwB,CA0G1B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The shape reconcile's reads, planned by the SQLite each adapter actually runs (#2083).
|
|
2
|
+
* The shape reconcile's reads, planned by the SQLite each adapter actually runs (#2083, #2154).
|
|
3
3
|
*
|
|
4
4
|
* A pass walks a shape's markers on `_substrat_tuples_shape_marker`, a partial index SQLite uses
|
|
5
5
|
* only where it can prove the index's `relation = 'bootstrap'` from the query. A relation term it
|
|
@@ -10,22 +10,33 @@
|
|
|
10
10
|
* kernel's `SwitchSql` (over a `better-sqlite3` file, a Durable Object's `storage.sql`) and
|
|
11
11
|
* asserts on the report.
|
|
12
12
|
*
|
|
13
|
+
* The backfill reads in windows too (#2154), for each holder kind: the probe seeds legacy holders
|
|
14
|
+
* of a `'self'`, a `'grantee'` and a table-holder shape, and reports each kind's read plans, how
|
|
15
|
+
* many rows each pass fetched, and how many markers the backfill wrote, so an adapter can assert
|
|
16
|
+
* every holder was marked exactly once. Given the engine's own count of rows read (a Durable
|
|
17
|
+
* Object's `cursor.rowsRead`), it reports the most any one pass read, every statement included,
|
|
18
|
+
* which is the bound a scope actually pays.
|
|
19
|
+
*
|
|
13
20
|
* Harness code: it writes `_substrat_*` rows directly, which is exactly what `ctx.sql` refuses.
|
|
14
21
|
*/
|
|
15
22
|
import { entityGrantShape } from '@substrat-run/contracts';
|
|
16
|
-
import { topUpEntityGrantShapes, ulid } from '@substrat-run/kernel';
|
|
23
|
+
import { SHAPE_MARKER_READS_PER_ROW, topUpEntityGrantShapes, ulid } from '@substrat-run/kernel';
|
|
17
24
|
const MARKERS = 30;
|
|
18
|
-
const
|
|
25
|
+
const LIMIT = 4;
|
|
19
26
|
const KEYS = ['shape-plan:read', 'shape-plan:use'];
|
|
20
27
|
const RETIRED = 'shape-plan:old';
|
|
28
|
+
const BADGES = 'shape_plan_badge';
|
|
21
29
|
/**
|
|
22
|
-
* Seed
|
|
23
|
-
* retired key, and another type's markers beside them — then reconcile
|
|
24
|
-
* resuming from the last, planning every statement.
|
|
30
|
+
* Seed three shapes' worlds on `raw` — holders of the old shape, legacy holders with no marker for
|
|
31
|
+
* each holder kind, a retired key, and another type's markers beside them — then reconcile them in
|
|
32
|
+
* small passes, each resuming from the last, planning every statement. `legacy` legacy holders per
|
|
33
|
+
* kind (default 5); `rowsRead`, when the engine counts them, the running total of rows it has read.
|
|
34
|
+
* Run it on a scope nothing else is writing.
|
|
25
35
|
*/
|
|
26
|
-
export function shapeReconcilePlans(raw, ids) {
|
|
36
|
+
export function shapeReconcilePlans(raw, ids, opts = {}) {
|
|
37
|
+
const legacy = opts.legacy ?? 5;
|
|
27
38
|
const tuple = (subject, relation, object) => raw.run('INSERT OR REPLACE INTO _substrat_tuples (subject, relation, object) VALUES (?, ?, ?)', subject, relation, object);
|
|
28
|
-
const pad = (i) => String(i).padStart(
|
|
39
|
+
const pad = (i) => String(i).padStart(5, '0');
|
|
29
40
|
for (let i = 0; i < MARKERS; i++) {
|
|
30
41
|
const [who, desk] = [`principal:${ulid()}`, `desk:d${pad(i)}`];
|
|
31
42
|
tuple(who, 'bootstrap', desk);
|
|
@@ -34,11 +45,21 @@ export function shapeReconcilePlans(raw, ids) {
|
|
|
34
45
|
tuple(who, `granted:${RETIRED}`, desk);
|
|
35
46
|
tuple(who, 'bootstrap', `room:d${pad(i)}`); // another type's marker: never read by this shape
|
|
36
47
|
}
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
raw.run(`CREATE TABLE IF NOT EXISTS ${BADGES} (id TEXT PRIMARY KEY, holder TEXT)`);
|
|
49
|
+
for (let i = 0; i < legacy; i++) {
|
|
50
|
+
const [grantee, owner, badged] = [ulid(), ulid(), ulid()];
|
|
51
|
+
tuple(`principal:${grantee}`, `granted:${KEYS[0]}`, `desk:l${pad(i)}`);
|
|
52
|
+
tuple(`principal:${owner}`, `granted:${KEYS[0]}`, `seat:${owner}`);
|
|
53
|
+
tuple(`principal:${ulid()}`, `granted:${KEYS[0]}`, `seat:${owner}`); // a sharee on it: never marked
|
|
54
|
+
raw.run(`INSERT INTO ${BADGES} (id, holder) VALUES (?, ?)`, `b${pad(i)}`, badged);
|
|
55
|
+
tuple(`principal:${badged}`, `granted:${KEYS[0]}`, `badge:b${pad(i)}`);
|
|
56
|
+
}
|
|
39
57
|
const walks = [];
|
|
40
|
-
const backfills = [];
|
|
58
|
+
const backfills = { self: [], grantee: [], table: [] };
|
|
41
59
|
const scans = [];
|
|
60
|
+
let [marks, fetched] = [0, 0];
|
|
61
|
+
/** Which holder kind a statement is the backfill read of, if it is one. */
|
|
62
|
+
const backfillOf = (sql, params) => sql.includes(`FROM "${BADGES}" e`) ? 'table' : sql.includes('(t.object, t.relation, t.subject) > (?, ?, ?)') ? (params.includes('seat;') ? 'self' : 'grantee') : null;
|
|
42
63
|
const plan = (sql, params) => {
|
|
43
64
|
if (!/^\s*(SELECT|UPDATE|DELETE)/i.test(sql))
|
|
44
65
|
return;
|
|
@@ -46,42 +67,77 @@ export function shapeReconcilePlans(raw, ids) {
|
|
|
46
67
|
const joined = detail.join(' | ');
|
|
47
68
|
if (sql.includes('(m.object, m.subject) > (?, ?)'))
|
|
48
69
|
walks.push(joined);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
70
|
+
const kind = backfillOf(sql, params);
|
|
71
|
+
if (kind)
|
|
72
|
+
backfills[kind].push(joined);
|
|
73
|
+
// A virtual table (`json_each`, `pragma_table_list`) is scanned by design; a scan of the tuple
|
|
74
|
+
// table, or of the holder's table, never is.
|
|
52
75
|
for (const d of detail)
|
|
53
76
|
if (/^SCAN /.test(d) && !/VIRTUAL TABLE/.test(d))
|
|
54
77
|
scans.push(`${d} — ${sql.replace(/\s+/g, ' ').slice(0, 160)}`);
|
|
55
78
|
};
|
|
56
79
|
const sql = {
|
|
57
80
|
all: (q, ...p) => {
|
|
81
|
+
if (q.startsWith('EXPLAIN'))
|
|
82
|
+
return raw.all(q, ...p); // the kernel's own plan check: not a read of rows
|
|
58
83
|
plan(q, p);
|
|
59
|
-
|
|
84
|
+
const rows = raw.all(q, ...p);
|
|
85
|
+
if (backfillOf(q, p))
|
|
86
|
+
fetched += rows.length;
|
|
87
|
+
return rows;
|
|
60
88
|
},
|
|
61
89
|
run: (q, ...p) => {
|
|
62
90
|
plan(q, p);
|
|
91
|
+
if (q.startsWith('INSERT OR IGNORE') && p[1] === 'bootstrap')
|
|
92
|
+
marks++;
|
|
63
93
|
raw.run(q, ...p);
|
|
64
94
|
},
|
|
65
95
|
};
|
|
66
|
-
const shapes = [
|
|
96
|
+
const shapes = [
|
|
97
|
+
entityGrantShape.parse({ entityType: 'desk', permissions: KEYS, retired: [RETIRED], bootstrap: true, holder: 'grantee' }),
|
|
98
|
+
entityGrantShape.parse({ entityType: 'seat', permissions: KEYS, bootstrap: true, holder: 'self' }),
|
|
99
|
+
entityGrantShape.parse({ entityType: 'badge', permissions: KEYS, bootstrap: true, holder: { table: BADGES, idColumn: 'id', principalColumn: 'holder' } }),
|
|
100
|
+
];
|
|
67
101
|
let after = null;
|
|
68
102
|
let passes = 0;
|
|
103
|
+
let maxFetched = 0;
|
|
104
|
+
let maxRowsRead = 0;
|
|
69
105
|
do {
|
|
106
|
+
fetched = 0;
|
|
107
|
+
const before = opts.rowsRead?.() ?? 0;
|
|
70
108
|
after = topUpEntityGrantShapes(sql, {
|
|
71
109
|
...ids,
|
|
72
110
|
shapes,
|
|
73
111
|
now: new Date().toISOString(),
|
|
74
|
-
limit:
|
|
112
|
+
limit: LIMIT,
|
|
75
113
|
after,
|
|
76
114
|
mintEventId: () => ulid(),
|
|
77
115
|
version: null,
|
|
78
116
|
}).next;
|
|
117
|
+
maxFetched = Math.max(maxFetched, fetched);
|
|
118
|
+
// `EXPLAIN QUERY PLAN` executes nothing, so the probe's own planning adds no rows read.
|
|
119
|
+
maxRowsRead = Math.max(maxRowsRead, (opts.rowsRead?.() ?? 0) - before);
|
|
79
120
|
passes++;
|
|
80
|
-
} while (after && passes <
|
|
121
|
+
} while (after && passes < 100_000);
|
|
81
122
|
const short = raw.all(`SELECT count(*) AS n FROM _substrat_tuples m
|
|
82
|
-
WHERE m.relation = 'bootstrap' AND m.object >= 'desk:' AND m.object < 'desk;'
|
|
123
|
+
WHERE m.relation = 'bootstrap' AND ((m.object >= 'desk:' AND m.object < 'desk;') OR (m.object >= 'seat:' AND m.object < 'seat;')
|
|
124
|
+
OR (m.object >= 'badge:' AND m.object < 'badge;'))
|
|
83
125
|
AND NOT EXISTS (SELECT 1 FROM _substrat_tuples t WHERE t.subject = m.subject AND t.object = m.object
|
|
84
126
|
AND t.relation = 'granted:${KEYS[1]}' AND t.revoked_at IS NULL)`)[0]['n'];
|
|
85
|
-
|
|
127
|
+
const markers = raw.all(`SELECT count(*) AS n FROM _substrat_tuples WHERE relation = 'bootstrap'
|
|
128
|
+
AND ((object >= 'desk:l' AND object < 'desk:m') OR (object >= 'seat:' AND object < 'seat;') OR (object >= 'badge:' AND object < 'badge;'))`)[0]['n'];
|
|
129
|
+
const holders = legacy * 3;
|
|
130
|
+
return {
|
|
131
|
+
walks,
|
|
132
|
+
backfills,
|
|
133
|
+
scans,
|
|
134
|
+
passes,
|
|
135
|
+
complete: after === null && Number(short) === 0 && Number(markers) === holders,
|
|
136
|
+
marks,
|
|
137
|
+
holders,
|
|
138
|
+
maxFetched,
|
|
139
|
+
window: LIMIT * SHAPE_MARKER_READS_PER_ROW,
|
|
140
|
+
maxRowsRead: opts.rowsRead ? maxRowsRead : null,
|
|
141
|
+
};
|
|
86
142
|
}
|
|
87
143
|
//# sourceMappingURL=shape-reconcile-plan.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shape-reconcile-plan.js","sourceRoot":"","sources":["../src/shape-reconcile-plan.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"shape-reconcile-plan.js","sourceRoot":"","sources":["../src/shape-reconcile-plan.ts"],"names":[],"mappings":"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"}
|