@quereus/quereus 4.3.2 → 4.4.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/core/database-external-changes.d.ts +2 -2
- package/dist/src/core/database-external-changes.d.ts.map +1 -1
- package/dist/src/core/database-external-changes.js +12 -9
- package/dist/src/core/database-external-changes.js.map +1 -1
- package/dist/src/core/database-materialized-views-apply.d.ts +156 -14
- package/dist/src/core/database-materialized-views-apply.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views-apply.js +370 -65
- package/dist/src/core/database-materialized-views-apply.js.map +1 -1
- package/dist/src/core/database-materialized-views-plan-builders.d.ts +65 -1
- package/dist/src/core/database-materialized-views-plan-builders.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views-plan-builders.js +347 -8
- package/dist/src/core/database-materialized-views-plan-builders.js.map +1 -1
- package/dist/src/core/database-materialized-views-plans.d.ts +254 -30
- package/dist/src/core/database-materialized-views-plans.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views.d.ts +96 -40
- package/dist/src/core/database-materialized-views.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views.js +172 -116
- package/dist/src/core/database-materialized-views.js.map +1 -1
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +20 -17
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
- package/dist/src/func/builtins/aggregate.js +127 -21
- package/dist/src/func/builtins/aggregate.js.map +1 -1
- package/dist/src/func/registration.d.ts +6 -1
- package/dist/src/func/registration.d.ts.map +1 -1
- package/dist/src/func/registration.js +16 -0
- package/dist/src/func/registration.js.map +1 -1
- package/dist/src/planner/analysis/equi-correlation.d.ts +26 -0
- package/dist/src/planner/analysis/equi-correlation.d.ts.map +1 -0
- package/dist/src/planner/analysis/equi-correlation.js +62 -0
- package/dist/src/planner/analysis/equi-correlation.js.map +1 -0
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +54 -18
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -1
- package/dist/src/planner/analysis/query-rewrite-matcher.js +86 -42
- package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -1
- package/dist/src/planner/analysis/scalar-subqueries.d.ts +28 -0
- package/dist/src/planner/analysis/scalar-subqueries.d.ts.map +1 -0
- package/dist/src/planner/analysis/scalar-subqueries.js +60 -0
- package/dist/src/planner/analysis/scalar-subqueries.js.map +1 -0
- package/dist/src/planner/building/select.d.ts.map +1 -1
- package/dist/src/planner/building/select.js +15 -1
- package/dist/src/planner/building/select.js.map +1 -1
- package/dist/src/planner/building/with.d.ts.map +1 -1
- package/dist/src/planner/building/with.js +5 -8
- package/dist/src/planner/building/with.js.map +1 -1
- package/dist/src/planner/cache/materialization-advisory.d.ts +33 -0
- package/dist/src/planner/cache/materialization-advisory.d.ts.map +1 -1
- package/dist/src/planner/cache/materialization-advisory.js +164 -28
- package/dist/src/planner/cache/materialization-advisory.js.map +1 -1
- package/dist/src/planner/cache/reference-graph.d.ts +0 -4
- package/dist/src/planner/cache/reference-graph.d.ts.map +1 -1
- package/dist/src/planner/cache/reference-graph.js +5 -13
- package/dist/src/planner/cache/reference-graph.js.map +1 -1
- package/dist/src/planner/cost/index.d.ts +28 -7
- package/dist/src/planner/cost/index.d.ts.map +1 -1
- package/dist/src/planner/cost/index.js +26 -0
- package/dist/src/planner/cost/index.js.map +1 -1
- package/dist/src/planner/nodes/cache-node.d.ts +18 -2
- package/dist/src/planner/nodes/cache-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/cache-node.js +42 -3
- package/dist/src/planner/nodes/cache-node.js.map +1 -1
- package/dist/src/planner/nodes/cte-node.d.ts +15 -1
- package/dist/src/planner/nodes/cte-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/cte-node.js +12 -2
- package/dist/src/planner/nodes/cte-node.js.map +1 -1
- package/dist/src/planner/nodes/recursive-cte-node.d.ts +30 -1
- package/dist/src/planner/nodes/recursive-cte-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/recursive-cte-node.js +30 -3
- package/dist/src/planner/nodes/recursive-cte-node.js.map +1 -1
- package/dist/src/planner/optimizer.d.ts.map +1 -1
- package/dist/src/planner/optimizer.js +129 -1
- package/dist/src/planner/optimizer.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-cte-optimization.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-cte-optimization.js +8 -1
- package/dist/src/planner/rules/cache/rule-cte-optimization.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts +7 -0
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.js +14 -1
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +53 -54
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.js +6 -0
- package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts +25 -0
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts.map +1 -0
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js +176 -0
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js.map +1 -0
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts +29 -0
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts.map +1 -0
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js +66 -0
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js.map +1 -0
- package/dist/src/planner/rules/join/rule-fanout-lookup-join.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-fanout-lookup-join.js +1 -54
- package/dist/src/planner/rules/join/rule-fanout-lookup-join.js.map +1 -1
- package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.d.ts +191 -0
- package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.d.ts.map +1 -0
- package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js +623 -0
- package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js.map +1 -0
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts +10 -1
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +242 -51
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
- package/dist/src/runtime/cache/shared-cache.d.ts +7 -0
- package/dist/src/runtime/cache/shared-cache.d.ts.map +1 -1
- package/dist/src/runtime/cache/shared-cache.js +34 -1
- package/dist/src/runtime/cache/shared-cache.js.map +1 -1
- package/dist/src/runtime/emit/aggregate.d.ts +2 -3
- package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/aggregate.js +6 -15
- package/dist/src/runtime/emit/aggregate.js.map +1 -1
- package/dist/src/runtime/emit/cache.d.ts +7 -3
- package/dist/src/runtime/emit/cache.d.ts.map +1 -1
- package/dist/src/runtime/emit/cache.js +23 -6
- package/dist/src/runtime/emit/cache.js.map +1 -1
- package/dist/src/runtime/emit/cte.d.ts.map +1 -1
- package/dist/src/runtime/emit/cte.js +43 -20
- package/dist/src/runtime/emit/cte.js.map +1 -1
- package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
- package/dist/src/runtime/emit/dml-executor.js +68 -43
- package/dist/src/runtime/emit/dml-executor.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.js +5 -4
- package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
- package/dist/src/runtime/emit/recursive-cte.d.ts.map +1 -1
- package/dist/src/runtime/emit/recursive-cte.js +59 -1
- package/dist/src/runtime/emit/recursive-cte.js.map +1 -1
- package/dist/src/runtime/parallel-driver.d.ts +15 -1
- package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
- package/dist/src/runtime/parallel-driver.js +17 -1
- package/dist/src/runtime/parallel-driver.js.map +1 -1
- package/dist/src/runtime/types.d.ts +36 -0
- package/dist/src/runtime/types.d.ts.map +1 -1
- package/dist/src/runtime/types.js.map +1 -1
- package/dist/src/schema/function.d.ts +79 -0
- package/dist/src/schema/function.d.ts.map +1 -1
- package/dist/src/schema/function.js.map +1 -1
- package/dist/src/vtab/memory/layer/scan-layer.js +21 -2
- package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
- package/package.json +3 -3
|
@@ -42,8 +42,8 @@ import { type SqlValue, type Row } from '../common/types.js';
|
|
|
42
42
|
import type { BackingRowChange } from '../vtab/backing-host.js';
|
|
43
43
|
import type { MaintainedTableSchema } from '../schema/derivation.js';
|
|
44
44
|
import type { UniqueConstraintSchema } from '../schema/table.js';
|
|
45
|
-
import type { MaterializedViewManagerContext, BackingConnectionCache } from './database-materialized-views-plans.js';
|
|
46
|
-
export type { BackingConnectionCache } from './database-materialized-views-plans.js';
|
|
45
|
+
import type { MaterializedViewManagerContext, ResidualKeyBatch, BackingConnectionCache } from './database-materialized-views-plans.js';
|
|
46
|
+
export type { BackingConnectionCache, ResidualKeyBatch } from './database-materialized-views-plans.js';
|
|
47
47
|
export declare class MaterializedViewManager {
|
|
48
48
|
private readonly ctx;
|
|
49
49
|
private unsubscribeSchemaChanges;
|
|
@@ -237,48 +237,95 @@ export declare class MaterializedViewManager {
|
|
|
237
237
|
* too. Omitted by the cold enforcement/eviction callers, which re-resolve the same
|
|
238
238
|
* connection deterministically.
|
|
239
239
|
*
|
|
240
|
-
* `deferred` is the optional per-statement deferred-rebuild set (MV keys)
|
|
241
|
-
* `
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
* once at the end-of-statement
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
240
|
+
* `deferred` is the optional per-statement deferred-rebuild set (MV keys) and
|
|
241
|
+
* `residualBatch` the optional per-statement residual key batch. A `'full-rebuild'`
|
|
242
|
+
* plan re-evaluates the WHOLE body, so applying it per source row is O(rows × body) —
|
|
243
|
+
* pathological; when the DML boundary supplies a `deferred` set it is instead marked
|
|
244
|
+
* dirty here (no per-row apply) and rebuilt exactly once at the end-of-statement
|
|
245
|
+
* {@link flushDeferredMaintenance} boundary. The three **residual** arms
|
|
246
|
+
* (`'residual-recompute'`, `'prefix-delete'`, `'join-residual'`) are likewise per-key
|
|
247
|
+
* recomputes whose per-row apply costs a scheduler run each; when the DML boundary
|
|
248
|
+
* supplies a `residualBatch`, they accumulate their affected binding keys (deduped
|
|
249
|
+
* across the statement) and recompute once per distinct key at the same flush.
|
|
250
|
+
* `'inverse-projection'` alone stays per-row-immediate: its delta is a cheap pure
|
|
251
|
+
* projection AND the covering-UNIQUE enforcement scan depends on its per-row backing
|
|
252
|
+
* visibility ({@link lookupCoveringConflicts} reads only inverse-projection backings,
|
|
253
|
+
* so deferring the other arms cannot starve that scan —
|
|
254
|
+
* {@link findRowTimeCoveringStructure} declines them). A cold caller without the
|
|
255
|
+
* per-statement structures falls through to the inline per-change apply — a safe,
|
|
256
|
+
* unamortized fallback (the enforcement/eviction callers only ever name
|
|
257
|
+
* inverse-projection MVs, but the fallback stays correct for every arm).
|
|
250
258
|
*/
|
|
251
|
-
maintainRowTime(sourceBase: string, change: BackingRowChange, cache?: BackingConnectionCache, deferred?: Set<string>, depth?: number): Promise<void>;
|
|
259
|
+
maintainRowTime(sourceBase: string, change: BackingRowChange, cache?: BackingConnectionCache, deferred?: Set<string>, residualBatch?: ResidualKeyBatch, depth?: number): Promise<void>;
|
|
252
260
|
/**
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
261
|
+
* The invariant pipeline every realized maintenance delta runs, in order: coarsening
|
|
262
|
+
* collision telemetry (observe-only; gated on `coarseningWatch`), the NOT-NULL
|
|
263
|
+
* ordering-seeded-PK guard (no-op unless the MV carries the skew), derived-row
|
|
264
|
+
* CHECK/FK validation (no-op for a constraint-less table; BEFORE the cascade so a
|
|
265
|
+
* consumer never consumes an invalid producer row), parent-side referential
|
|
266
|
+
* enforcement (RESTRICT-walk then declared actions, after `M`'s own image is
|
|
267
|
+
* validated), then the MV-over-MV cascade — each effective {@link BackingRowChange}
|
|
268
|
+
* routed back through {@link maintainRowTime} with the SAME per-statement structures,
|
|
269
|
+
* so a residual consumer accumulates into the batch and a full-rebuild consumer
|
|
270
|
+
* re-dirties the deferred set. Shared verbatim by the per-row inline path and the
|
|
271
|
+
* end-of-statement flush ({@link flushDeferredMaintenance}, which calls with
|
|
272
|
+
* `depth = 0` — its own round bound {@link assertFlushRounds} replaces the recursion
|
|
273
|
+
* bound {@link assertCascadeDepth} there).
|
|
274
|
+
*/
|
|
275
|
+
private postApplyBackingChanges;
|
|
276
|
+
/**
|
|
277
|
+
* Flush the per-statement deferred maintenance at the end-of-statement boundary:
|
|
278
|
+
* recompute every accumulated residual key exactly once per distinct key
|
|
279
|
+
* ({@link applyResidualBatch}) and rebuild every dirtied full-rebuild MV exactly once
|
|
280
|
+
* (not once per source row), cascading each apply's effective
|
|
281
|
+
* {@link BackingRowChange}(s) onward so MV-over-MV consumers converge.
|
|
257
282
|
*
|
|
258
|
-
* Drained as a worklist over the producer→consumer DAG. Each
|
|
259
|
-
* {@link
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
283
|
+
* Drained as a worklist over the producer→consumer DAG. Each apply routes its realized
|
|
284
|
+
* delta back through {@link maintainRowTime} with the SAME `deferred` set and
|
|
285
|
+
* `residualBatch`: an inverse-projection consumer applies inline; a residual consumer
|
|
286
|
+
* accumulates its keys into the batch and a full-rebuild consumer re-dirties into the
|
|
287
|
+
* drain (recomputed in a later round, after its producer's delta has landed). The drain
|
|
288
|
+
* proceeds in **rounds** — each round snapshots both structures, clears them, and
|
|
289
|
+
* applies each member, collecting the next round's re-accumulations — so a consumer is
|
|
290
|
+
* never permanently stale (a producer flushed in the same round re-accumulates it for
|
|
291
|
+
* the next), and convergence takes at most one round per level of the deferred sub-DAG.
|
|
292
|
+
* Residual entries drain before rebuilds within a round (arbitrary for correctness —
|
|
293
|
+
* every apply reads live state — but it lets a same-round rebuild read its residual
|
|
294
|
+
* producers' already-flushed backings, saving a round in mixed chains).
|
|
268
295
|
*
|
|
269
296
|
* Termination: the dependency DAG is acyclic (a consumer MV requires its producer to
|
|
270
|
-
* pre-exist), so the longest
|
|
297
|
+
* pre-exist), so the longest deferred chain — hence the round count — is bounded by
|
|
271
298
|
* the registered-row-time-MV count. Exceeding it signals a structurally-impossible cycle
|
|
272
299
|
* and fails loud ({@link assertFlushRounds}) — the worklist analogue of
|
|
273
300
|
* {@link assertCascadeDepth}. This should never fire.
|
|
274
301
|
*
|
|
275
302
|
* The DML executor calls this INSIDE the statement-atomicity savepoint (after the row
|
|
276
|
-
* loop, before the savepoint release), so a failed rebuild
|
|
277
|
-
*
|
|
303
|
+
* loop, before the savepoint release), so a failed recompute/rebuild — including a
|
|
304
|
+
* derived-row validation failure or parent-side RESTRICT over the flushed delta — rolls
|
|
305
|
+
* the whole statement back with the same attribution as the per-row path. Empty
|
|
306
|
+
* structures are a no-op (no overhead on statements touching no deferred MV).
|
|
278
307
|
*/
|
|
279
|
-
|
|
308
|
+
flushDeferredMaintenance(deferred: Set<string>, residualBatch: ResidualKeyBatch, cache?: BackingConnectionCache): Promise<void>;
|
|
280
309
|
/**
|
|
281
|
-
*
|
|
310
|
+
* Flush one residual-arm MV's accumulated statement keys: run the residual variant
|
|
311
|
+
* once per distinct key against live post-statement state and apply all resulting ops
|
|
312
|
+
* in one {@link BackingHost.applyMaintenance} call (upsert recomputed slices, delete
|
|
313
|
+
* emptied keys — the same keyed diff the per-row path applies, evaluated once per key
|
|
314
|
+
* instead of once per touching row).
|
|
315
|
+
*
|
|
316
|
+
* **Per-statement rebuild demotion** ({@link shouldDegradeToRebuild}, re-costed here
|
|
317
|
+
* against the statement's ACTUAL distinct-key count and the plan's create-time
|
|
318
|
+
* {@link MaintenancePlanCommon.sourceStats}): when k residual runs cost more than one
|
|
319
|
+
* whole-body rebuild — a statement touching most groups — run the plan's
|
|
320
|
+
* {@link ResidualArmCommon.fullRebuildScheduler} and apply a single `'replace-all'`
|
|
321
|
+
* keyed diff instead. Stateless: the stored strategy is unchanged, so a later
|
|
322
|
+
* low-cardinality statement reverts to per-key residuals. Kept on the manager
|
|
323
|
+
* (delegating op computation to the apply-module free helpers) so the statement-flush
|
|
324
|
+
* suites can instrument it alongside {@link applyFullRebuild}.
|
|
325
|
+
*/
|
|
326
|
+
private applyResidualBatch;
|
|
327
|
+
/**
|
|
328
|
+
* Round backstop for {@link flushDeferredMaintenance}. The full-rebuild sub-DAG is acyclic,
|
|
282
329
|
* so the drain converges in at most one round per chain level — bounded by the row-time
|
|
283
330
|
* MV count. A round count beyond that (`+1` slack for an initial dirty set already
|
|
284
331
|
* spanning multiple levels) signals a structural impossibility (a cycle) — fail loud
|
|
@@ -323,13 +370,20 @@ export declare class MaterializedViewManager {
|
|
|
323
370
|
*
|
|
324
371
|
* Unlike the bounded-delta arms this ignores the specific changed row — the floor
|
|
325
372
|
* rebuilds wholesale. It is therefore deferred to a single end-of-statement flush
|
|
326
|
-
* ({@link
|
|
373
|
+
* ({@link flushDeferredMaintenance}) rather than run per source row, so a bulk statement
|
|
327
374
|
* rebuilds exactly once; this is that one rebuild. An empty body (zero rows) yields a
|
|
328
375
|
* `'replace-all' []`, which empties the backing. Kept on the manager (delegating the
|
|
329
376
|
* scan / backing write to the apply-module free helpers) so the per-statement-flush
|
|
330
377
|
* deferral suite can instrument it to count rebuilds.
|
|
331
378
|
*/
|
|
332
379
|
private applyFullRebuild;
|
|
380
|
+
/**
|
|
381
|
+
* Run a whole-body scheduler to completion against live mid-transaction source state
|
|
382
|
+
* and apply the single `'replace-all'` keyed diff. The shared kernel of the
|
|
383
|
+
* full-rebuild floor ({@link applyFullRebuild}) and the residual arms' per-statement
|
|
384
|
+
* rebuild demotion ({@link applyResidualBatch}).
|
|
385
|
+
*/
|
|
386
|
+
private applyReplaceAll;
|
|
333
387
|
/**
|
|
334
388
|
* Resolve the linked, enforcement-ready covering MV for a UNIQUE constraint on
|
|
335
389
|
* `schema.table`, or `undefined`. The constraint's `coveringStructureName`
|
|
@@ -337,13 +391,15 @@ export declare class MaterializedViewManager {
|
|
|
337
391
|
* this confirms a live row-time plan exists for the source, the MV is not
|
|
338
392
|
* `stale` (structural breakage), and the plan is **per-row maintained** — only
|
|
339
393
|
* then is its backing table row-time consistent enough to answer conflict
|
|
340
|
-
* resolution.
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
394
|
+
* resolution. Only the `'inverse-projection'` arm qualifies: every other arm —
|
|
395
|
+
* the `'full-rebuild'` floor and the residual arms — is deferred to the
|
|
396
|
+
* end-of-statement flush (its backing lags the source mid-statement), so it can
|
|
397
|
+
* never serve as a covering structure for a synchronous per-row UNIQUE probe —
|
|
398
|
+
* all are skipped here regardless of any (informational) `coveringStructureName`
|
|
399
|
+
* link, which keeps an eligibility flip from opening a stale-read enforcement
|
|
400
|
+
* path. O(1) negative fast path off {@link rowTimeBySource} so a source table
|
|
401
|
+
* with no row-time covering MV pays a single map lookup and stays on the
|
|
402
|
+
* synchronous index/scan path.
|
|
347
403
|
*
|
|
348
404
|
* **Collation eligibility gate.** A covering MV generates its conflict candidates
|
|
349
405
|
* by re-comparing each backing row under the SOURCE column's DECLARED collation
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-materialized-views.d.ts","sourceRoot":"","sources":["../../../src/core/database-materialized-views.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAKH,OAAO,EAAc,KAAK,QAAQ,EAAE,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAIzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,KAAK,EAAe,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"database-materialized-views.d.ts","sourceRoot":"","sources":["../../../src/core/database-materialized-views.ts"],"names":[],"mappings":"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"}
|
|
@@ -48,8 +48,9 @@ import { compareSqlValuesFast, resolveCollationFunctions } from '../util/compari
|
|
|
48
48
|
import { coveringMvHonorsIndexCollation } from '../schema/unique-enforcement.js';
|
|
49
49
|
import { splitBaseKey } from '../util/qualified-name.js';
|
|
50
50
|
import { mvKey, planSourceBases, isBinaryCollation, } from './database-materialized-views-analysis.js';
|
|
51
|
+
import { shouldDegradeToRebuild } from '../planner/cost/index.js';
|
|
51
52
|
import { buildMaintenancePlan } from './database-materialized-views-plan-builders.js';
|
|
52
|
-
import { applyInverseProjection, applyForwardResidual, applyJoinResidual, applyPrefixDelete, runScheduler, validateDerivedChanges, assertNoNullInNotNullSeededPkRowTime, enforceParentSideReferentialActions,
|
|
53
|
+
import { applyInverseProjection, applyForwardResidual, applyJoinResidual, applyPrefixDelete, accumulateResidualKeys, computeResidualBatchOps, computeDeltaAggregateOps, resolveBackingApplyTarget, runScheduler, validateDerivedChanges, assertNoNullInNotNullSeededPkRowTime, enforceParentSideReferentialActions, } from './database-materialized-views-apply.js';
|
|
53
54
|
const log = createLogger('core:materialized-views');
|
|
54
55
|
export class MaterializedViewManager {
|
|
55
56
|
ctx;
|
|
@@ -552,18 +553,26 @@ export class MaterializedViewManager {
|
|
|
552
553
|
* too. Omitted by the cold enforcement/eviction callers, which re-resolve the same
|
|
553
554
|
* connection deterministically.
|
|
554
555
|
*
|
|
555
|
-
* `deferred` is the optional per-statement deferred-rebuild set (MV keys)
|
|
556
|
-
* `
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
* once at the end-of-statement
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
556
|
+
* `deferred` is the optional per-statement deferred-rebuild set (MV keys) and
|
|
557
|
+
* `residualBatch` the optional per-statement residual key batch. A `'full-rebuild'`
|
|
558
|
+
* plan re-evaluates the WHOLE body, so applying it per source row is O(rows × body) —
|
|
559
|
+
* pathological; when the DML boundary supplies a `deferred` set it is instead marked
|
|
560
|
+
* dirty here (no per-row apply) and rebuilt exactly once at the end-of-statement
|
|
561
|
+
* {@link flushDeferredMaintenance} boundary. The three **residual** arms
|
|
562
|
+
* (`'residual-recompute'`, `'prefix-delete'`, `'join-residual'`) are likewise per-key
|
|
563
|
+
* recomputes whose per-row apply costs a scheduler run each; when the DML boundary
|
|
564
|
+
* supplies a `residualBatch`, they accumulate their affected binding keys (deduped
|
|
565
|
+
* across the statement) and recompute once per distinct key at the same flush.
|
|
566
|
+
* `'inverse-projection'` alone stays per-row-immediate: its delta is a cheap pure
|
|
567
|
+
* projection AND the covering-UNIQUE enforcement scan depends on its per-row backing
|
|
568
|
+
* visibility ({@link lookupCoveringConflicts} reads only inverse-projection backings,
|
|
569
|
+
* so deferring the other arms cannot starve that scan —
|
|
570
|
+
* {@link findRowTimeCoveringStructure} declines them). A cold caller without the
|
|
571
|
+
* per-statement structures falls through to the inline per-change apply — a safe,
|
|
572
|
+
* unamortized fallback (the enforcement/eviction callers only ever name
|
|
573
|
+
* inverse-projection MVs, but the fallback stays correct for every arm).
|
|
565
574
|
*/
|
|
566
|
-
async maintainRowTime(sourceBase, change, cache, deferred, depth = 0) {
|
|
575
|
+
async maintainRowTime(sourceBase, change, cache, deferred, residualBatch, depth = 0) {
|
|
567
576
|
const changedBase = sourceBase.toLowerCase();
|
|
568
577
|
const keys = this.rowTimeBySource.get(changedBase);
|
|
569
578
|
if (!keys || keys.size === 0)
|
|
@@ -572,80 +581,103 @@ export class MaterializedViewManager {
|
|
|
572
581
|
const plan = this.rowTime.get(key);
|
|
573
582
|
if (!plan)
|
|
574
583
|
continue;
|
|
575
|
-
// Full-rebuild
|
|
584
|
+
// Full-rebuild defers as a dirty mark; the residual arms defer as accumulated
|
|
585
|
+
// binding keys. Both drain at the end-of-statement flush.
|
|
576
586
|
if (plan.kind === 'full-rebuild' && deferred) {
|
|
577
587
|
deferred.add(key);
|
|
578
588
|
continue;
|
|
579
589
|
}
|
|
580
|
-
|
|
581
|
-
|
|
590
|
+
if (residualBatch && plan.kind !== 'inverse-projection' && plan.kind !== 'full-rebuild') {
|
|
591
|
+
accumulateResidualKeys(plan, change, changedBase, residualBatch, key);
|
|
582
592
|
continue;
|
|
583
|
-
// Row-time coarsening collision telemetry: observe-only over the realized
|
|
584
|
-
// delta (gated on `coarseningWatch` — a no-op for a non-coarsened MV). Runs
|
|
585
|
-
// independently of the cascade below; it neither consumes nor reorders the
|
|
586
|
-
// backing changes routed onward.
|
|
587
|
-
this.detectAndReportCoarseningCollisions(plan, backingChanges);
|
|
588
|
-
// Reject a NULL stored into a NOT-NULL ordering-seeded physical-PK backing
|
|
589
|
-
// column whose source loosened — BEFORE the cascade, so the corruption never
|
|
590
|
-
// reaches a consumer. No-op (`undefined`) unless this MV carries the skew.
|
|
591
|
-
assertNoNullInNotNullSeededPkRowTime(plan, backingChanges);
|
|
592
|
-
// Declared CHECK / child-side FK over the rows this delta wrote — BEFORE
|
|
593
|
-
// cascading, so a consumer never consumes an invalid producer row. Every
|
|
594
|
-
// row already in the backing was validated when it entered (the bulk
|
|
595
|
-
// validation at create/attach seeds the induction), so only the delta is
|
|
596
|
-
// validated. No-op (`undefined`) for a constraint-less table.
|
|
597
|
-
if (plan.derivedRowValidator) {
|
|
598
|
-
await validateDerivedChanges(this.ctx, plan, plan.derivedRowValidator, backingChanges, cache);
|
|
599
|
-
}
|
|
600
|
-
// Parent-side referential enforcement: this maintenance delete/key-update of an
|
|
601
|
-
// `M` row may orphan rows in an ordinary table `C` whose FK references `M`. Fire
|
|
602
|
-
// the shared engine over the backing delta — RESTRICT-walk then declared actions —
|
|
603
|
-
// after `M`'s own image is validated, before the MV-over-MV cascade. Runs whether
|
|
604
|
-
// or not `M` has MV consumers (placed before the leaf fast-path).
|
|
605
|
-
await enforceParentSideReferentialActions(this.ctx, plan, backingChanges);
|
|
606
|
-
const backingBase = `${plan.backingSchema}.${plan.backingTableName}`.toLowerCase();
|
|
607
|
-
if (!this.rowTimeBySource.has(backingBase))
|
|
608
|
-
continue; // leaf — no dependents
|
|
609
|
-
this.assertCascadeDepth(depth + 1, backingBase);
|
|
610
|
-
for (const bc of backingChanges) {
|
|
611
|
-
await this.maintainRowTime(backingBase, bc, cache, deferred, depth + 1);
|
|
612
593
|
}
|
|
594
|
+
const backingChanges = await this.applyMaintenancePlan(plan, change, changedBase, cache);
|
|
595
|
+
await this.postApplyBackingChanges(plan, backingChanges, cache, deferred, residualBatch, depth + 1);
|
|
613
596
|
}
|
|
614
597
|
}
|
|
615
598
|
/**
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
599
|
+
* The invariant pipeline every realized maintenance delta runs, in order: coarsening
|
|
600
|
+
* collision telemetry (observe-only; gated on `coarseningWatch`), the NOT-NULL
|
|
601
|
+
* ordering-seeded-PK guard (no-op unless the MV carries the skew), derived-row
|
|
602
|
+
* CHECK/FK validation (no-op for a constraint-less table; BEFORE the cascade so a
|
|
603
|
+
* consumer never consumes an invalid producer row), parent-side referential
|
|
604
|
+
* enforcement (RESTRICT-walk then declared actions, after `M`'s own image is
|
|
605
|
+
* validated), then the MV-over-MV cascade — each effective {@link BackingRowChange}
|
|
606
|
+
* routed back through {@link maintainRowTime} with the SAME per-statement structures,
|
|
607
|
+
* so a residual consumer accumulates into the batch and a full-rebuild consumer
|
|
608
|
+
* re-dirties the deferred set. Shared verbatim by the per-row inline path and the
|
|
609
|
+
* end-of-statement flush ({@link flushDeferredMaintenance}, which calls with
|
|
610
|
+
* `depth = 0` — its own round bound {@link assertFlushRounds} replaces the recursion
|
|
611
|
+
* bound {@link assertCascadeDepth} there).
|
|
612
|
+
*/
|
|
613
|
+
async postApplyBackingChanges(plan, backingChanges, cache, deferred, residualBatch, depth) {
|
|
614
|
+
if (backingChanges.length === 0)
|
|
615
|
+
return;
|
|
616
|
+
this.detectAndReportCoarseningCollisions(plan, backingChanges);
|
|
617
|
+
assertNoNullInNotNullSeededPkRowTime(plan, backingChanges);
|
|
618
|
+
if (plan.derivedRowValidator) {
|
|
619
|
+
await validateDerivedChanges(this.ctx, plan, plan.derivedRowValidator, backingChanges, cache);
|
|
620
|
+
}
|
|
621
|
+
await enforceParentSideReferentialActions(this.ctx, plan, backingChanges);
|
|
622
|
+
const backingBase = `${plan.backingSchema}.${plan.backingTableName}`.toLowerCase();
|
|
623
|
+
if (!this.rowTimeBySource.has(backingBase))
|
|
624
|
+
return; // leaf — no dependents
|
|
625
|
+
if (depth > 0)
|
|
626
|
+
this.assertCascadeDepth(depth, backingBase);
|
|
627
|
+
for (const bc of backingChanges) {
|
|
628
|
+
await this.maintainRowTime(backingBase, bc, cache, deferred, residualBatch, depth);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Flush the per-statement deferred maintenance at the end-of-statement boundary:
|
|
633
|
+
* recompute every accumulated residual key exactly once per distinct key
|
|
634
|
+
* ({@link applyResidualBatch}) and rebuild every dirtied full-rebuild MV exactly once
|
|
635
|
+
* (not once per source row), cascading each apply's effective
|
|
636
|
+
* {@link BackingRowChange}(s) onward so MV-over-MV consumers converge.
|
|
620
637
|
*
|
|
621
|
-
* Drained as a worklist over the producer→consumer DAG. Each
|
|
622
|
-
* {@link
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
638
|
+
* Drained as a worklist over the producer→consumer DAG. Each apply routes its realized
|
|
639
|
+
* delta back through {@link maintainRowTime} with the SAME `deferred` set and
|
|
640
|
+
* `residualBatch`: an inverse-projection consumer applies inline; a residual consumer
|
|
641
|
+
* accumulates its keys into the batch and a full-rebuild consumer re-dirties into the
|
|
642
|
+
* drain (recomputed in a later round, after its producer's delta has landed). The drain
|
|
643
|
+
* proceeds in **rounds** — each round snapshots both structures, clears them, and
|
|
644
|
+
* applies each member, collecting the next round's re-accumulations — so a consumer is
|
|
645
|
+
* never permanently stale (a producer flushed in the same round re-accumulates it for
|
|
646
|
+
* the next), and convergence takes at most one round per level of the deferred sub-DAG.
|
|
647
|
+
* Residual entries drain before rebuilds within a round (arbitrary for correctness —
|
|
648
|
+
* every apply reads live state — but it lets a same-round rebuild read its residual
|
|
649
|
+
* producers' already-flushed backings, saving a round in mixed chains).
|
|
631
650
|
*
|
|
632
651
|
* Termination: the dependency DAG is acyclic (a consumer MV requires its producer to
|
|
633
|
-
* pre-exist), so the longest
|
|
652
|
+
* pre-exist), so the longest deferred chain — hence the round count — is bounded by
|
|
634
653
|
* the registered-row-time-MV count. Exceeding it signals a structurally-impossible cycle
|
|
635
654
|
* and fails loud ({@link assertFlushRounds}) — the worklist analogue of
|
|
636
655
|
* {@link assertCascadeDepth}. This should never fire.
|
|
637
656
|
*
|
|
638
657
|
* The DML executor calls this INSIDE the statement-atomicity savepoint (after the row
|
|
639
|
-
* loop, before the savepoint release), so a failed rebuild
|
|
640
|
-
*
|
|
658
|
+
* loop, before the savepoint release), so a failed recompute/rebuild — including a
|
|
659
|
+
* derived-row validation failure or parent-side RESTRICT over the flushed delta — rolls
|
|
660
|
+
* the whole statement back with the same attribution as the per-row path. Empty
|
|
661
|
+
* structures are a no-op (no overhead on statements touching no deferred MV).
|
|
641
662
|
*/
|
|
642
|
-
async
|
|
663
|
+
async flushDeferredMaintenance(deferred, residualBatch, cache) {
|
|
643
664
|
let round = 0;
|
|
644
|
-
while (deferred.size > 0) {
|
|
665
|
+
while (deferred.size > 0 || residualBatch.size > 0) {
|
|
645
666
|
this.assertFlushRounds(++round);
|
|
646
|
-
const
|
|
667
|
+
const residuals = [...residualBatch.entries()];
|
|
668
|
+
residualBatch.clear();
|
|
669
|
+
const rebuilds = [...deferred];
|
|
647
670
|
deferred.clear();
|
|
648
|
-
for (const key of
|
|
671
|
+
for (const [key, entry] of residuals) {
|
|
672
|
+
const plan = this.rowTime.get(key);
|
|
673
|
+
// Only residual-arm plans ever accumulate keys; a plan released (or
|
|
674
|
+
// re-registered as a different arm) mid-statement is a no-op here.
|
|
675
|
+
if (!plan || plan.kind === 'inverse-projection' || plan.kind === 'full-rebuild')
|
|
676
|
+
continue;
|
|
677
|
+
const backingChanges = await this.applyResidualBatch(plan, entry, cache);
|
|
678
|
+
await this.postApplyBackingChanges(plan, backingChanges, cache, deferred, residualBatch, 0);
|
|
679
|
+
}
|
|
680
|
+
for (const key of rebuilds) {
|
|
649
681
|
const plan = this.rowTime.get(key);
|
|
650
682
|
// Only full-rebuild plans are ever deferred; a non-full-rebuild key (or a
|
|
651
683
|
// plan released mid-flush) is a no-op. Defensive — `maintainRowTime` only
|
|
@@ -653,41 +685,58 @@ export class MaterializedViewManager {
|
|
|
653
685
|
if (!plan || plan.kind !== 'full-rebuild')
|
|
654
686
|
continue;
|
|
655
687
|
const backingChanges = await this.applyFullRebuild(plan, cache);
|
|
656
|
-
|
|
657
|
-
continue;
|
|
658
|
-
// Coarsening collision telemetry over the rebuild diff — the full-rebuild
|
|
659
|
-
// floor's collation-keyed `replace-all` realizes the same LWW merge as the
|
|
660
|
-
// bounded-delta arms (observe-only; gated on `coarseningWatch`).
|
|
661
|
-
this.detectAndReportCoarseningCollisions(plan, backingChanges);
|
|
662
|
-
// NOT-NULL ordering-seeded PK guard over the rebuild diff — the
|
|
663
|
-
// full-rebuild-floor analogue of the per-row guard in {@link maintainRowTime}.
|
|
664
|
-
// No-op (`undefined`) unless this MV carries the skew.
|
|
665
|
-
assertNoNullInNotNullSeededPkRowTime(plan, backingChanges);
|
|
666
|
-
// Validate the rebuild diff's written images at the flush boundary —
|
|
667
|
-
// the full-rebuild analogue of the per-row validation in
|
|
668
|
-
// {@link maintainRowTime} (deferred-rebuild semantics preserved: a bulk
|
|
669
|
-
// source write fails once at end-of-statement, not per source row).
|
|
670
|
-
if (plan.derivedRowValidator) {
|
|
671
|
-
await validateDerivedChanges(this.ctx, plan, plan.derivedRowValidator, backingChanges, cache);
|
|
672
|
-
}
|
|
673
|
-
// Parent-side referential enforcement for the rebuild diff's deletes/key-updates,
|
|
674
|
-
// fired inside the statement-atomicity savepoint (the flush runs before its
|
|
675
|
-
// release) so a RESTRICT failure or cascade error unwinds the whole statement.
|
|
676
|
-
await enforceParentSideReferentialActions(this.ctx, plan, backingChanges);
|
|
677
|
-
const backingBase = `${plan.backingSchema}.${plan.backingTableName}`.toLowerCase();
|
|
678
|
-
if (!this.rowTimeBySource.has(backingBase))
|
|
679
|
-
continue; // leaf — no dependents
|
|
680
|
-
for (const bc of backingChanges) {
|
|
681
|
-
// Cascade at depth 0: an incremental consumer applies inline (its own
|
|
682
|
-
// `assertCascadeDepth` backstops that recursion); a full-rebuild consumer
|
|
683
|
-
// re-dirties `deferred` for the next round.
|
|
684
|
-
await this.maintainRowTime(backingBase, bc, cache, deferred);
|
|
685
|
-
}
|
|
688
|
+
await this.postApplyBackingChanges(plan, backingChanges, cache, deferred, residualBatch, 0);
|
|
686
689
|
}
|
|
687
690
|
}
|
|
688
691
|
}
|
|
689
692
|
/**
|
|
690
|
-
*
|
|
693
|
+
* Flush one residual-arm MV's accumulated statement keys: run the residual variant
|
|
694
|
+
* once per distinct key against live post-statement state and apply all resulting ops
|
|
695
|
+
* in one {@link BackingHost.applyMaintenance} call (upsert recomputed slices, delete
|
|
696
|
+
* emptied keys — the same keyed diff the per-row path applies, evaluated once per key
|
|
697
|
+
* instead of once per touching row).
|
|
698
|
+
*
|
|
699
|
+
* **Per-statement rebuild demotion** ({@link shouldDegradeToRebuild}, re-costed here
|
|
700
|
+
* against the statement's ACTUAL distinct-key count and the plan's create-time
|
|
701
|
+
* {@link MaintenancePlanCommon.sourceStats}): when k residual runs cost more than one
|
|
702
|
+
* whole-body rebuild — a statement touching most groups — run the plan's
|
|
703
|
+
* {@link ResidualArmCommon.fullRebuildScheduler} and apply a single `'replace-all'`
|
|
704
|
+
* keyed diff instead. Stateless: the stored strategy is unchanged, so a later
|
|
705
|
+
* low-cardinality statement reverts to per-key residuals. Kept on the manager
|
|
706
|
+
* (delegating op computation to the apply-module free helpers) so the statement-flush
|
|
707
|
+
* suites can instrument it alongside {@link applyFullRebuild}.
|
|
708
|
+
*/
|
|
709
|
+
async applyResidualBatch(plan, entry, cache) {
|
|
710
|
+
const distinctKeys = entry.forward.size + entry.lookup.size + entry.prefix.size;
|
|
711
|
+
if (distinctKeys === 0)
|
|
712
|
+
return [];
|
|
713
|
+
// Delta-aggregate fast path: an eligible aggregate MV's accumulated per-group
|
|
714
|
+
// deltas are applied by arithmetic read-modify-write on the stored group rows
|
|
715
|
+
// (computeDeltaAggregateOps) — zero source reads. It BYPASSES the
|
|
716
|
+
// degrade-to-rebuild crossover below: that crossover trades k residual
|
|
717
|
+
// re-executions against one whole-body rebuild, and the delta path already
|
|
718
|
+
// costs O(affected groups) with no residual runs, so the rebuild can never win.
|
|
719
|
+
// A poisoned accumulation (OR FAIL per-row revert — see
|
|
720
|
+
// poisonResidualDeltaAccumulations) has `delta` dropped and falls through to
|
|
721
|
+
// the plain residual over the always-accumulated forward keys.
|
|
722
|
+
if (plan.kind === 'residual-recompute' && plan.delta && entry.delta && !entry.deltaPoisoned) {
|
|
723
|
+
const { host, connection } = await resolveBackingApplyTarget(this.ctx, plan, cache);
|
|
724
|
+
const ops = await computeDeltaAggregateOps(this.ctx, plan, entry, host, connection);
|
|
725
|
+
if (ops.length === 0)
|
|
726
|
+
return [];
|
|
727
|
+
return host.applyMaintenance(connection, ops);
|
|
728
|
+
}
|
|
729
|
+
if (plan.fullRebuildScheduler && shouldDegradeToRebuild(distinctKeys, plan.sourceStats)) {
|
|
730
|
+
return this.applyReplaceAll(plan, plan.fullRebuildScheduler, cache);
|
|
731
|
+
}
|
|
732
|
+
const { host, connection } = await resolveBackingApplyTarget(this.ctx, plan, cache);
|
|
733
|
+
const ops = await computeResidualBatchOps(this.ctx, plan, entry, host, connection);
|
|
734
|
+
if (ops.length === 0)
|
|
735
|
+
return [];
|
|
736
|
+
return host.applyMaintenance(connection, ops);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Round backstop for {@link flushDeferredMaintenance}. The full-rebuild sub-DAG is acyclic,
|
|
691
740
|
* so the drain converges in at most one round per chain level — bounded by the row-time
|
|
692
741
|
* MV count. A round count beyond that (`+1` slack for an initial dirty set already
|
|
693
742
|
* spanning multiple levels) signals a structural impossibility (a cycle) — fail loud
|
|
@@ -763,20 +812,24 @@ export class MaterializedViewManager {
|
|
|
763
812
|
*
|
|
764
813
|
* Unlike the bounded-delta arms this ignores the specific changed row — the floor
|
|
765
814
|
* rebuilds wholesale. It is therefore deferred to a single end-of-statement flush
|
|
766
|
-
* ({@link
|
|
815
|
+
* ({@link flushDeferredMaintenance}) rather than run per source row, so a bulk statement
|
|
767
816
|
* rebuilds exactly once; this is that one rebuild. An empty body (zero rows) yields a
|
|
768
817
|
* `'replace-all' []`, which empties the backing. Kept on the manager (delegating the
|
|
769
818
|
* scan / backing write to the apply-module free helpers) so the per-statement-flush
|
|
770
819
|
* deferral suite can instrument it to count rebuilds.
|
|
771
820
|
*/
|
|
772
821
|
async applyFullRebuild(plan, cache) {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
822
|
+
return this.applyReplaceAll(plan, plan.bodyScheduler, cache);
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Run a whole-body scheduler to completion against live mid-transaction source state
|
|
826
|
+
* and apply the single `'replace-all'` keyed diff. The shared kernel of the
|
|
827
|
+
* full-rebuild floor ({@link applyFullRebuild}) and the residual arms' per-statement
|
|
828
|
+
* rebuild demotion ({@link applyResidualBatch}).
|
|
829
|
+
*/
|
|
830
|
+
async applyReplaceAll(plan, bodyScheduler, cache) {
|
|
831
|
+
const rows = await runScheduler(this.ctx, bodyScheduler, {});
|
|
832
|
+
const { host, connection } = await resolveBackingApplyTarget(this.ctx, plan, cache);
|
|
780
833
|
return host.applyMaintenance(connection, [{ kind: 'replace-all', rows }]);
|
|
781
834
|
}
|
|
782
835
|
/* ──────────────── row-time covering enforcement ──────────────── */
|
|
@@ -787,13 +840,15 @@ export class MaterializedViewManager {
|
|
|
787
840
|
* this confirms a live row-time plan exists for the source, the MV is not
|
|
788
841
|
* `stale` (structural breakage), and the plan is **per-row maintained** — only
|
|
789
842
|
* then is its backing table row-time consistent enough to answer conflict
|
|
790
|
-
* resolution.
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
796
|
-
*
|
|
843
|
+
* resolution. Only the `'inverse-projection'` arm qualifies: every other arm —
|
|
844
|
+
* the `'full-rebuild'` floor and the residual arms — is deferred to the
|
|
845
|
+
* end-of-statement flush (its backing lags the source mid-statement), so it can
|
|
846
|
+
* never serve as a covering structure for a synchronous per-row UNIQUE probe —
|
|
847
|
+
* all are skipped here regardless of any (informational) `coveringStructureName`
|
|
848
|
+
* link, which keeps an eligibility flip from opening a stale-read enforcement
|
|
849
|
+
* path. O(1) negative fast path off {@link rowTimeBySource} so a source table
|
|
850
|
+
* with no row-time covering MV pays a single map lookup and stays on the
|
|
851
|
+
* synchronous index/scan path.
|
|
797
852
|
*
|
|
798
853
|
* **Collation eligibility gate.** A covering MV generates its conflict candidates
|
|
799
854
|
* by re-comparing each backing row under the SOURCE column's DECLARED collation
|
|
@@ -829,9 +884,11 @@ export class MaterializedViewManager {
|
|
|
829
884
|
const mv = plan.mv;
|
|
830
885
|
if (mv.name !== mvName)
|
|
831
886
|
continue; // must be THE linked covering MV
|
|
832
|
-
//
|
|
833
|
-
// the
|
|
834
|
-
|
|
887
|
+
// Only the inverse-projection arm is per-row maintained; every other arm —
|
|
888
|
+
// the full-rebuild floor AND the residual arms — is reconciled at the
|
|
889
|
+
// end-of-statement flush, so its backing lags the source mid-statement and
|
|
890
|
+
// cannot answer a synchronous per-row UNIQUE probe.
|
|
891
|
+
if (plan.kind !== 'inverse-projection')
|
|
835
892
|
return undefined;
|
|
836
893
|
if (mv.derivation.stale)
|
|
837
894
|
return undefined; // not row-time consistent
|
|
@@ -937,8 +994,7 @@ export class MaterializedViewManager {
|
|
|
937
994
|
const backing = this.ctx.schemaManager.getTable(plan.backingSchema, plan.backingTableName);
|
|
938
995
|
if (!backing)
|
|
939
996
|
return [];
|
|
940
|
-
const host =
|
|
941
|
-
const connection = await getBackingConnection(this.ctx, host, `${plan.backingSchema}.${plan.backingTableName}`);
|
|
997
|
+
const { host, connection } = await resolveBackingApplyTarget(this.ctx, plan);
|
|
942
998
|
// Both comparisons below run per scanned backing row, so resolve their collations
|
|
943
999
|
// once here, against this database (a redefined NOCASE must be honored).
|
|
944
1000
|
//
|