@quereus/quereus 4.13.1 → 4.14.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/src/func/builtins/explain.js +1 -1
- package/dist/src/func/builtins/explain.js.map +1 -1
- package/dist/src/planner/mutation/capture-correlation.d.ts +57 -0
- package/dist/src/planner/mutation/capture-correlation.d.ts.map +1 -0
- package/dist/src/planner/mutation/capture-correlation.js +57 -0
- package/dist/src/planner/mutation/capture-correlation.js.map +1 -0
- package/dist/src/planner/mutation/decomposition.d.ts.map +1 -1
- package/dist/src/planner/mutation/decomposition.js +86 -23
- package/dist/src/planner/mutation/decomposition.js.map +1 -1
- package/dist/src/planner/mutation/lens-enforcement.d.ts.map +1 -1
- package/dist/src/planner/mutation/lens-enforcement.js +15 -20
- package/dist/src/planner/mutation/lens-enforcement.js.map +1 -1
- package/dist/src/planner/mutation/multi-source.d.ts +52 -11
- package/dist/src/planner/mutation/multi-source.d.ts.map +1 -1
- package/dist/src/planner/mutation/multi-source.js +273 -86
- package/dist/src/planner/mutation/multi-source.js.map +1 -1
- package/dist/src/planner/mutation/set-op.d.ts.map +1 -1
- package/dist/src/planner/mutation/set-op.js +4 -12
- package/dist/src/planner/mutation/set-op.js.map +1 -1
- package/dist/src/planner/mutation/single-source.js +1 -1
- package/dist/src/planner/mutation/single-source.js.map +1 -1
- package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/alter-table.js +8 -10
- package/dist/src/runtime/emit/alter-table.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts +16 -4
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.js +182 -25
- package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view.js +1 -1
- package/dist/src/runtime/emit/materialized-view.js.map +1 -1
- package/dist/src/schema/ddl-generator.d.ts.map +1 -1
- package/dist/src/schema/ddl-generator.js +65 -24
- package/dist/src/schema/ddl-generator.js.map +1 -1
- package/dist/src/schema/lens-ack.d.ts +1 -1
- package/dist/src/schema/lens-ack.js +2 -2
- package/dist/src/schema/lens-compiler.d.ts.map +1 -1
- package/dist/src/schema/lens-compiler.js +63 -34
- package/dist/src/schema/lens-compiler.js.map +1 -1
- package/dist/src/schema/lens-prover.d.ts +4 -4
- package/dist/src/schema/lens-prover.d.ts.map +1 -1
- package/dist/src/schema/lens-prover.js +13 -5
- package/dist/src/schema/lens-prover.js.map +1 -1
- package/dist/src/schema/lens.d.ts +1 -1
- package/dist/src/schema/manager.d.ts.map +1 -1
- package/dist/src/schema/manager.js +25 -13
- package/dist/src/schema/manager.js.map +1 -1
- package/dist/src/schema/reserved-tags.js +1 -1
- package/dist/src/schema/reserved-tags.js.map +1 -1
- package/dist/src/schema/schema-differ.d.ts.map +1 -1
- package/dist/src/schema/schema-differ.js +4 -3
- package/dist/src/schema/schema-differ.js.map +1 -1
- package/dist/src/schema/table.d.ts +41 -34
- package/dist/src/schema/table.d.ts.map +1 -1
- package/dist/src/schema/table.js +24 -43
- package/dist/src/schema/table.js.map +1 -1
- package/dist/src/vtab/memory/layer/alter-column.d.ts +8 -1
- package/dist/src/vtab/memory/layer/alter-column.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/alter-column.js +20 -10
- package/dist/src/vtab/memory/layer/alter-column.js.map +1 -1
- package/dist/src/vtab/memory/layer/base.d.ts +9 -1
- package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/base.js +12 -1
- package/dist/src/vtab/memory/layer/base.js.map +1 -1
- package/dist/src/vtab/memory/layer/manager.d.ts +23 -18
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +44 -33
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/dist/src/vtab/memory/layer/row-convert.d.ts +13 -0
- package/dist/src/vtab/memory/layer/row-convert.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/row-convert.js +24 -0
- package/dist/src/vtab/memory/layer/row-convert.js.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.d.ts +21 -7
- package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.js +30 -14
- package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
- package/dist/src/vtab/module.d.ts +20 -2
- package/dist/src/vtab/module.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -41,9 +41,9 @@ export function materializedViewNotASetError(schemaName, viewName) {
|
|
|
41
41
|
* the schema declares NOT NULL *and* that is a physical-PK member. This is the exact
|
|
42
42
|
* reachable contradiction: the view's `order by <col>` seeded the column into the
|
|
43
43
|
* backing's **physical** primary key ({@link computeBackingPrimaryKey}), which keeps it
|
|
44
|
-
* declared NOT NULL even after the source column loosened to nullable —
|
|
45
|
-
*
|
|
46
|
-
*
|
|
44
|
+
* declared NOT NULL even after the source column loosened to nullable — the refresh reshape
|
|
45
|
+
* masks the loosening of a physical-PK column rather than emitting it (see
|
|
46
|
+
* {@link isPhysicalPkColumn}). Once the
|
|
47
47
|
* source column becomes nullable and the recomputed body yields a NULL row, storing it
|
|
48
48
|
* would leave the backing schema declaring NOT NULL while holding a NULL — a silent
|
|
49
49
|
* contradiction. Both maintenance vectors raise this instead, naming the column, the cause,
|
|
@@ -73,11 +73,20 @@ export function nullInNotNullSeededPkError(schemaName, viewName, columnName) {
|
|
|
73
73
|
* `keysOf` (all-columns fallback when none — such an MV is incremental-ineligible
|
|
74
74
|
* until Phase 2). Re-planning here is cheap relative to materialization and keeps
|
|
75
75
|
* the create/refresh emitters free of optimizer plumbing.
|
|
76
|
+
*
|
|
77
|
+
* `backing` names the module that hosts (or would host) this shape's backing table,
|
|
78
|
+
* plus (for a comparison) the table the result will be compared against. Supplying it
|
|
79
|
+
* runs the derived shape through that module's own create-time normalization
|
|
80
|
+
* ({@link normalizeBackingShape}) so every downstream comparison sees the shape the
|
|
81
|
+
* module would actually create — see that function for why, and for what `against`
|
|
82
|
+
* resolves. Omit only where no backing identity exists; every consumer that compares
|
|
83
|
+
* against a live backing must pass it.
|
|
76
84
|
*/
|
|
77
|
-
export function deriveBackingShape(db, schemaName, bodySql, explicitColumns) {
|
|
85
|
+
export function deriveBackingShape(db, schemaName, bodySql, explicitColumns, backing) {
|
|
78
86
|
// Suppress the read-side rewrite: we are computing the MV body to derive/populate
|
|
79
87
|
// its OWN backing, so it must not be rewritten to read that backing.
|
|
80
|
-
|
|
88
|
+
const shape = db.schemaManager.withSuppressedMaterializedViewRewrite(() => deriveBackingShapeUnguarded(db, schemaName, bodySql, explicitColumns));
|
|
89
|
+
return normalizeBackingShape(db, schemaName, shape, backing?.moduleName, backing?.moduleArgs, backing?.against);
|
|
81
90
|
}
|
|
82
91
|
function deriveBackingShapeUnguarded(db, schemaName, bodySql, explicitColumns) {
|
|
83
92
|
// Home-schema path: the body's unqualified source names resolve next to the
|
|
@@ -278,6 +287,146 @@ tags) {
|
|
|
278
287
|
tags: tags && Object.keys(tags).length > 0 ? tags : undefined,
|
|
279
288
|
};
|
|
280
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Applies the backing module's own create-time schema normalization
|
|
292
|
+
* ({@link VirtualTableModule.normalizeCreateSchema}) to a derived {@link BackingShape},
|
|
293
|
+
* so a shape derived from the body is expressed in the SAME terms as a table the module
|
|
294
|
+
* actually created.
|
|
295
|
+
*
|
|
296
|
+
* Why: a module may own per-column physical attributes the body's output type cannot
|
|
297
|
+
* predict. The store module keys an *implicit*-collation text primary-key column under
|
|
298
|
+
* its table-level key collation K (default NOCASE — `reconcilePkCollations`), so a
|
|
299
|
+
* persisted backing reads `COLLATE NOCASE` while the re-derived shape says BINARY. Every
|
|
300
|
+
* shape comparison ({@link backingShapeMatches}, {@link describeBackingShapeMismatch},
|
|
301
|
+
* {@link classifyBackingReshape}) would then report a difference that does not exist,
|
|
302
|
+
* refilling the backing on every reopen. Normalizing here — at the SOURCE, before any
|
|
303
|
+
* comparison — keeps both sides post-normalization rather than teaching each comparison
|
|
304
|
+
* site to special-case a module's rewrite.
|
|
305
|
+
*
|
|
306
|
+
* Identity for a module without the hook (memory, and every third-party module), and for
|
|
307
|
+
* an unresolvable module name — the real `no virtual table module named …` /
|
|
308
|
+
* capability diagnostics stay with {@link buildBackingTableSchema}, which is deliberately
|
|
309
|
+
* NOT reused here: it carries the `getBackingHost` gate (whose error must keep firing at
|
|
310
|
+
* its own site) and stamps primary-key flags onto `shape.columns`, neither of which
|
|
311
|
+
* belongs on a pure "what would this become?" probe.
|
|
312
|
+
*
|
|
313
|
+
* **`against` — two-valued compatibility for an implicit attribute.** The hook only ever
|
|
314
|
+
* rewrites attributes the shape left IMPLICIT (the store touches a text primary-key column
|
|
315
|
+
* only when it declares no `collate`). An implicit attribute is genuinely ambiguous once a
|
|
316
|
+
* table exists, because explicitness does not survive a catalog round-trip
|
|
317
|
+
* (`ColumnSchema.collationExplicit` is not persisted; a declared `collate binary` reloads
|
|
318
|
+
* indistinguishable from no clause at all). So an implicit shape column is compatible with
|
|
319
|
+
* a live column carrying EITHER the shape's own declared value or the module's normalized
|
|
320
|
+
* one — both are physically consistent with the body. When the caller names the table this
|
|
321
|
+
* shape will be compared against, the rewrite is therefore kept only where the RAW shape
|
|
322
|
+
* does not already agree with that table; a table-form maintained table whose text key was
|
|
323
|
+
* declared `collate binary` keeps matching its own declaration, while a materialized-view
|
|
324
|
+
* backing the module keyed under K matches that. Omit `against` on a create path (nothing
|
|
325
|
+
* to be compatible with yet) and the module's answer is taken verbatim.
|
|
326
|
+
*
|
|
327
|
+
* A collation the module would NOT produce and the body did NOT declare (a backing
|
|
328
|
+
* persisted under RTRIM, say) matches neither reading and still reports a mismatch. The
|
|
329
|
+
* agreement test looks only at the attributes the hook actually rewrote — see
|
|
330
|
+
* {@link rawReadingIsTheLiveOne}.
|
|
331
|
+
*
|
|
332
|
+
* Only `columns` can change; `primaryKey`, `ordering`, `sourceTables`, `coarsenedKey`,
|
|
333
|
+
* and `allProvedKeys` pass through untouched.
|
|
334
|
+
*/
|
|
335
|
+
function normalizeBackingShape(db, schemaName, shape, moduleName, moduleArgs, against) {
|
|
336
|
+
const resolvedModuleName = normalizeBackingModuleName(moduleName);
|
|
337
|
+
const module = db.schemaManager.getModule(resolvedModuleName)?.module;
|
|
338
|
+
if (!module?.normalizeCreateSchema)
|
|
339
|
+
return shape;
|
|
340
|
+
// Copy the columns into the probe: the contract says the hook is pure, but a
|
|
341
|
+
// sloppy implementation mutating a column in place would otherwise corrupt the
|
|
342
|
+
// caller's shape. The probe uses the PHYSICAL primary key
|
|
343
|
+
// ({@link computeBackingPrimaryKey} — ordering-seeded), not `shape.primaryKey`,
|
|
344
|
+
// because that is the key the module will actually be handed at create; a text
|
|
345
|
+
// ORDERING column is reconciled there too and would keep diverging otherwise.
|
|
346
|
+
const probeColumns = shape.columns.map(c => ({ ...c }));
|
|
347
|
+
const probe = {
|
|
348
|
+
name: `<materialized-view backing shape probe>`,
|
|
349
|
+
schemaName,
|
|
350
|
+
columns: probeColumns,
|
|
351
|
+
columnIndexMap: buildColumnIndexMap(probeColumns),
|
|
352
|
+
primaryKeyDefinition: computeBackingPrimaryKey(shape).map(pk => ({
|
|
353
|
+
index: pk.index,
|
|
354
|
+
desc: pk.desc,
|
|
355
|
+
collation: probeColumns[pk.index]?.collation,
|
|
356
|
+
})),
|
|
357
|
+
checkConstraints: [],
|
|
358
|
+
vtabModule: module,
|
|
359
|
+
vtabModuleName: resolvedModuleName,
|
|
360
|
+
vtabArgs: moduleArgs ? { ...moduleArgs } : {},
|
|
361
|
+
isView: false,
|
|
362
|
+
estimatedRows: 0,
|
|
363
|
+
};
|
|
364
|
+
const normalized = module.normalizeCreateSchema(probe);
|
|
365
|
+
if (normalized.columns === probe.columns)
|
|
366
|
+
return shape; // hook declined to rewrite
|
|
367
|
+
// A reshape is a contract violation, not a shape difference: the hook may only
|
|
368
|
+
// adjust per-column attributes it owns physically. Fail loud rather than let a
|
|
369
|
+
// misbehaving module silently redefine the backing's column list.
|
|
370
|
+
if (normalized.columns.length !== shape.columns.length) {
|
|
371
|
+
throw new QuereusError(`module '${resolvedModuleName}' normalizeCreateSchema changed the column count `
|
|
372
|
+
+ `(${shape.columns.length} → ${normalized.columns.length}); it may only adjust per-column attributes`, StatusCode.INTERNAL);
|
|
373
|
+
}
|
|
374
|
+
for (let i = 0; i < shape.columns.length; i++) {
|
|
375
|
+
if (normalized.columns[i].name !== shape.columns[i].name) {
|
|
376
|
+
throw new QuereusError(`module '${resolvedModuleName}' normalizeCreateSchema renamed or reordered column ${i} `
|
|
377
|
+
+ `('${shape.columns[i].name}' → '${normalized.columns[i].name}'); it may only adjust per-column attributes`, StatusCode.INTERNAL);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Two-valued compatibility (see the docblock): where the RAW shape column already
|
|
381
|
+
// agrees with the table this shape is about to be compared against, that reading is
|
|
382
|
+
// the live one and the module's alternative is not applied.
|
|
383
|
+
// NOTE: the two readings exist only because `ColumnSchema.collationExplicit` does not
|
|
384
|
+
// survive a catalog round-trip. If explicitness is ever persisted, the live table can
|
|
385
|
+
// answer "did the author declare this?" directly and this collapses to a single
|
|
386
|
+
// reading — take the module's answer iff the live column is implicit.
|
|
387
|
+
// NOTE: `against` is read POSITIONALLY, which is what the rename cascade needs (names
|
|
388
|
+
// have shifted there, the attributes have not). It is only ever consulted for a column
|
|
389
|
+
// the hook rewrote, so a pure append/drop reshape still resolves correctly — but if a
|
|
390
|
+
// reshape ever MOVES a rewritten column to a position the live table fills with a
|
|
391
|
+
// different column, this asks the wrong column and the alignment-based
|
|
392
|
+
// {@link classifyBackingReshape} would see a collation the module did not produce. Give
|
|
393
|
+
// callers a way to say "align by name" if a reshape is ever seen misclassified this way.
|
|
394
|
+
const columns = normalized.columns.map((col, i) => {
|
|
395
|
+
const raw = shape.columns[i];
|
|
396
|
+
if (col === raw)
|
|
397
|
+
return raw;
|
|
398
|
+
const live = against?.columns[i];
|
|
399
|
+
if (live && rawReadingIsTheLiveOne(raw, col, live))
|
|
400
|
+
return raw;
|
|
401
|
+
return col;
|
|
402
|
+
});
|
|
403
|
+
if (columns.every((c, i) => c === shape.columns[i]))
|
|
404
|
+
return shape;
|
|
405
|
+
return { ...shape, columns };
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* The two-valued compatibility test of {@link normalizeBackingShape}: does the RAW
|
|
409
|
+
* (pre-normalization) reading of a column agree with the live table on every attribute
|
|
410
|
+
* the hook actually rewrote?
|
|
411
|
+
*
|
|
412
|
+
* Only the REWRITTEN attributes are compared. An attribute the hook left alone carries no
|
|
413
|
+
* second reading, so a difference in it is a genuine shape change for the comparison sites
|
|
414
|
+
* to report — it says nothing about which reading of the rewritten attribute is live.
|
|
415
|
+
* Testing all three attributes unconditionally instead would let an unrelated difference
|
|
416
|
+
* decide the collation: a NOT NULL loosening on a physical-PK column — which
|
|
417
|
+
* {@link describeBackingShapeMismatch} deliberately masks — would flip a declared
|
|
418
|
+
* `collate binary` key over to the module's K and turn a data-only refresh into a
|
|
419
|
+
* spurious "primary-key column 0 collation BINARY → NOCASE" inexpressible reshape.
|
|
420
|
+
*/
|
|
421
|
+
function rawReadingIsTheLiveOne(raw, normalized, live) {
|
|
422
|
+
if (!backingTypeMatches(raw, normalized) && !backingTypeMatches(raw, live))
|
|
423
|
+
return false;
|
|
424
|
+
if (!backingNotNullMatches(raw, normalized) && !backingNotNullMatches(raw, live))
|
|
425
|
+
return false;
|
|
426
|
+
if (!backingCollationMatches(raw, normalized) && !backingCollationMatches(raw, live))
|
|
427
|
+
return false;
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
281
430
|
/** Runs the body to completion and returns its rows (raw `Row` arrays). Uses the
|
|
282
431
|
* no-transaction-management primitive — the caller is already inside DDL execution. */
|
|
283
432
|
export async function collectBodyRows(db, schemaName, bodySql) {
|
|
@@ -396,7 +545,7 @@ function warnKeyCoarsening(schemaName, viewName, info) {
|
|
|
396
545
|
*/
|
|
397
546
|
export async function materializeView(db, def, preDerivedShape) {
|
|
398
547
|
const sm = db.schemaManager;
|
|
399
|
-
const shape = preDerivedShape ?? deriveBackingShape(db, def.schemaName, def.bodySql, def.columns);
|
|
548
|
+
const shape = preDerivedShape ?? deriveBackingShape(db, def.schemaName, def.bodySql, def.columns, { moduleName: def.backingModuleName, moduleArgs: def.backingModuleArgs });
|
|
400
549
|
// Lives here — not in deriveBackingShape — because the refresh path reaches a
|
|
401
550
|
// legitimate mismatch after a source ALTER (see the assert's docstring).
|
|
402
551
|
assertDeclaredColumnArity(def, shape);
|
|
@@ -913,7 +1062,7 @@ discardBackingOnFailure = false) {
|
|
|
913
1062
|
// renamed positionally to it and the name check skipped; otherwise natural output
|
|
914
1063
|
// names with the strict declared-shape check (the body must already be aliased to
|
|
915
1064
|
// the declared names — the attach verb / implicit-create posture).
|
|
916
|
-
const shape = deriveBackingShape(db, schemaName, bodySql, positionalRename ? recordedColumns : undefined);
|
|
1065
|
+
const shape = deriveBackingShape(db, schemaName, bodySql, positionalRename ? recordedColumns : undefined, { moduleName: table.vtabModuleName, moduleArgs: table.vtabArgs, against: table });
|
|
917
1066
|
// Explicit rename-list arity guard. `deriveBackingShape` sizes the shape to the
|
|
918
1067
|
// BODY's arity (a surplus rename name is dropped, a missing one padded), so a
|
|
919
1068
|
// list whose length disagrees with the body would otherwise record an
|
|
@@ -1272,7 +1421,7 @@ export async function createMaintainedTable(db, stmt) {
|
|
|
1272
1421
|
const declared = sm.buildDeclaredTableSchema(stmt);
|
|
1273
1422
|
const recordedColumns = explicit ? declared.columns.map(c => c.name) : undefined;
|
|
1274
1423
|
const bodySql = astToString(stmt.maintained.select);
|
|
1275
|
-
const shape = deriveBackingShape(db, schemaName, bodySql, explicit ? recordedColumns : undefined);
|
|
1424
|
+
const shape = deriveBackingShape(db, schemaName, bodySql, explicit ? recordedColumns : undefined, { moduleName: declared.vtabModuleName, moduleArgs: declared.vtabArgs, against: declared });
|
|
1276
1425
|
const mismatch = describeAttachShapeMismatch(declared, shape, explicit);
|
|
1277
1426
|
if (mismatch) {
|
|
1278
1427
|
throw new QuereusError(`cannot create maintained table '${schemaName}.${name}': ${mismatch}`, StatusCode.ERROR, undefined, stmt.table.loc?.start.line, stmt.table.loc?.start.column);
|
|
@@ -1358,8 +1507,8 @@ export async function rebuildBacking(db, mv, validationColumns) {
|
|
|
1358
1507
|
// a backing column declared NOT NULL that is also a physical-PK member cannot legally
|
|
1359
1508
|
// hold the recomputed NULL a nullable source now produces. The ordering-seeded physical
|
|
1360
1509
|
// PK (computeBackingPrimaryKey) pins such a column NOT NULL even after its source column
|
|
1361
|
-
// dropped NOT NULL — the
|
|
1362
|
-
//
|
|
1510
|
+
// dropped NOT NULL — the reshape masks the loosening of a physical-PK column rather than
|
|
1511
|
+
// emitting it — so at this point the only still-NOT-NULL columns that can
|
|
1363
1512
|
// carry source-nullable data are the physical-PK ones. Storing the NULL would make the
|
|
1364
1513
|
// backing schema declare NOT NULL while holding a NULL; reject it before either swap.
|
|
1365
1514
|
// NOTE: narrow loud guard until `debt-mv-ordering-seed-to-materialized-index` replaces
|
|
@@ -1462,9 +1611,17 @@ function backingCollationMatches(a, b) {
|
|
|
1462
1611
|
/** Whether `columnNameLower` (already lowercased) names a column of `table`'s
|
|
1463
1612
|
* *physical* primary key ({@link TableSchema.primaryKeyDefinition} — which for an
|
|
1464
1613
|
* MV backing includes any ordering-seeded columns, {@link computeBackingPrimaryKey}).
|
|
1465
|
-
*
|
|
1466
|
-
*
|
|
1467
|
-
*
|
|
1614
|
+
* An MV backing keeps its physical-PK columns NOT NULL, so the refresh reshape must
|
|
1615
|
+
* never try to loosen one — see the two callers below.
|
|
1616
|
+
*
|
|
1617
|
+
* NOTE: that is now a POLICY of the MV backing, not an engine constraint. A primary key
|
|
1618
|
+
* column may be nullable in general (docs/schema.md § Primary-key nullability) and
|
|
1619
|
+
* `alter column … drop not null` on one is accepted by both backends; the backing keeps
|
|
1620
|
+
* its seeded key columns NOT NULL because the ordering seed treats them as a total order
|
|
1621
|
+
* and `assertNoNullInNotNullSeededPk` is the loud guard for the contradiction. If
|
|
1622
|
+
* ordering-seeded backings ever need to carry NULLs in the seeded column, this mask and
|
|
1623
|
+
* that guard are the pair to revisit together — `debt-mv-ordering-seed-to-materialized-index`
|
|
1624
|
+
* removes the need for both. */
|
|
1468
1625
|
function isPhysicalPkColumn(table, columnNameLower) {
|
|
1469
1626
|
return table.primaryKeyDefinition.some(def => table.columns[def.index]?.name.toLowerCase() === columnNameLower);
|
|
1470
1627
|
}
|
|
@@ -1487,8 +1644,8 @@ function describeBackingShapeMismatch(current, shape) {
|
|
|
1487
1644
|
}
|
|
1488
1645
|
if (!backingNotNullMatches(a, b)) {
|
|
1489
1646
|
// A physical-PK column stays NOT NULL in the backing regardless of the
|
|
1490
|
-
// re-derived logical nullability (
|
|
1491
|
-
//
|
|
1647
|
+
// re-derived logical nullability (an MV-backing policy — see
|
|
1648
|
+
// {@link isPhysicalPkColumn}), so a NOT-NULL→nullable *loosening*
|
|
1492
1649
|
// of a PK column is NOT a shape difference. Masking it lets refresh keep the
|
|
1493
1650
|
// data-only rebuild path instead of emitting a doomed `loosenNotNull` op. Tight
|
|
1494
1651
|
// on purpose: only a loosening (`current` NOT NULL, derived nullable) of a
|
|
@@ -1836,14 +1993,14 @@ export function tryRecompileMaterializedViewLive(db, mv, oldObject, newObject) {
|
|
|
1836
1993
|
try {
|
|
1837
1994
|
const d = mv.derivation;
|
|
1838
1995
|
const bodySql = astToString(d.selectAst);
|
|
1839
|
-
const
|
|
1996
|
+
const schema = db.schemaManager.getSchemaOrFail(mv.schemaName);
|
|
1997
|
+
const live = schema.getTable(mv.name);
|
|
1998
|
+
const backing = isMaintainedTable(live) ? live : mv;
|
|
1999
|
+
const shape = deriveBackingShape(db, mv.schemaName, bodySql, d.columns, { moduleName: mv.vtabModuleName, moduleArgs: mv.vtabArgs, against: backing });
|
|
1840
2000
|
if (!sameSourceTables(d.sourceTables, shape.sourceTables)) {
|
|
1841
2001
|
log('Marking materialized view %s.%s stale instead of recompiling: re-planned source tables (%s) disagree with the recorded set (%s) — REFRESH re-derives', mv.schemaName, mv.name, shape.sourceTables.join(', '), d.sourceTables.join(', '));
|
|
1842
2002
|
return false;
|
|
1843
2003
|
}
|
|
1844
|
-
const schema = db.schemaManager.getSchemaOrFail(mv.schemaName);
|
|
1845
|
-
const live = schema.getTable(mv.name);
|
|
1846
|
-
const backing = isMaintainedTable(live) ? live : mv;
|
|
1847
2004
|
const mismatch = describeBackingShapeMismatch(backing, shape);
|
|
1848
2005
|
if (mismatch) {
|
|
1849
2006
|
// Relaxed superkey gate: columns match structurally AND the existing backing
|
|
@@ -1964,10 +2121,10 @@ function classifyBackingReshape(current, shape) {
|
|
|
1964
2121
|
if (!backingNotNullMatches(from, to)) {
|
|
1965
2122
|
if (to.notNull === true)
|
|
1966
2123
|
postReconcileOps.push({ kind: 'tightenNotNull', name });
|
|
1967
|
-
//
|
|
1968
|
-
//
|
|
1969
|
-
//
|
|
1970
|
-
//
|
|
2124
|
+
// An MV backing keeps its physical-PK columns NOT NULL (a backing policy — see
|
|
2125
|
+
// {@link isPhysicalPkColumn}), so skip the `loosenNotNull` op for a physical-PK
|
|
2126
|
+
// column of `current` (matched by its pre-rename name) rather than emitting a
|
|
2127
|
+
// loosening the backing must not take. Reached only when reshape is entered for SOME OTHER genuine
|
|
1971
2128
|
// shape change coexisting with a PK-column loosening in the same refresh;
|
|
1972
2129
|
// touch point #1 masks the loosening-only case before reshape. See the NOTE at
|
|
1973
2130
|
// describeBackingShapeMismatch / computeBackingPrimaryKey (~line 236).
|
|
@@ -2713,7 +2870,7 @@ export async function restoreUnaffectedMaterializedViews(db, preStale) {
|
|
|
2713
2870
|
// Throws when the body no longer plans against the renamed catalog
|
|
2714
2871
|
// (e.g. a chained MV referencing a renamed-away output name) → catch
|
|
2715
2872
|
// below leaves it stale.
|
|
2716
|
-
const shape = deriveBackingShape(db, mv.schemaName, bodySql, d.columns);
|
|
2873
|
+
const shape = deriveBackingShape(db, mv.schemaName, bodySql, d.columns, { moduleName: mv.vtabModuleName, moduleArgs: mv.vtabArgs, against: schema.getTable(mv.name) });
|
|
2717
2874
|
// The retry of a failure-marked MV must not revive an inconsistent record: a
|
|
2718
2875
|
// rewrite that threw between the in-place AST mutation and the derived-field
|
|
2719
2876
|
// re-key leaves the OLD derivation (un-re-keyed `sourceTables`) holding the
|
|
@@ -2769,7 +2926,7 @@ function sameSourceTables(a, b) {
|
|
|
2769
2926
|
* rewrite (a mid-statement failure).
|
|
2770
2927
|
*/
|
|
2771
2928
|
async function renameShiftedBackingColumns(db, schema, mv, bodySql, preDerivedShape) {
|
|
2772
|
-
const shape = preDerivedShape ?? deriveBackingShape(db, mv.schemaName, bodySql, mv.derivation.columns);
|
|
2929
|
+
const shape = preDerivedShape ?? deriveBackingShape(db, mv.schemaName, bodySql, mv.derivation.columns, { moduleName: mv.vtabModuleName, moduleArgs: mv.vtabArgs, against: schema.getTable(mv.name) });
|
|
2773
2930
|
const backing = schema.getTable(mv.name);
|
|
2774
2931
|
if (!backing) {
|
|
2775
2932
|
throw new QuereusError(`Internal error: maintained table '${mv.name}' not found during backing-column rename`, StatusCode.INTERNAL);
|