@quereus/quereus 4.20.0 → 4.20.2
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/README.md +2 -0
- package/dist/src/common/types.d.ts +4 -4
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/schema/catalog-rendering.js +13 -1
- package/dist/src/schema/catalog-rendering.js.map +1 -1
- package/dist/src/schema/catalog.d.ts +35 -2
- package/dist/src/schema/catalog.d.ts.map +1 -1
- package/dist/src/schema/catalog.js +42 -33
- package/dist/src/schema/catalog.js.map +1 -1
- package/dist/src/schema/schema-differ.d.ts +1 -1
- package/dist/src/schema/schema-differ.d.ts.map +1 -1
- package/dist/src/schema/schema-differ.js +195 -33
- package/dist/src/schema/schema-differ.js.map +1 -1
- package/dist/src/schema/unique-enforcement.d.ts +23 -1
- package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
- package/dist/src/schema/unique-enforcement.js +43 -1
- package/dist/src/schema/unique-enforcement.js.map +1 -1
- package/dist/src/vtab/memory/layer/manager.d.ts +15 -12
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +87 -100
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/package.json +7 -4
- package/test/README.md +173 -0
- package/test/logic/01-basic.sqllogic +39 -0
- package/test/logic/01.1-select-projection-extras.sqllogic +130 -0
- package/test/logic/01.2-strings.sqllogic +43 -0
- package/test/logic/01.5-insert-select.sqllogic +237 -0
- package/test/logic/01.6-update-extras.sqllogic +118 -0
- package/test/logic/01.7-update-from.sqllogic +67 -0
- package/test/logic/01.8-delete-extras.sqllogic +122 -0
- package/test/logic/01.8.1-delete-range-cow.sqllogic +115 -0
- package/test/logic/01.9-query-expr-dml-gates.sqllogic +32 -0
- package/test/logic/01.9-query-expr-dml.sqllogic +171 -0
- package/test/logic/01.9-query-expr-values.sqllogic +111 -0
- package/test/logic/01.9.1-limit-over-dml-subquery.sqllogic +119 -0
- package/test/logic/02-filters.sqllogic +54 -0
- package/test/logic/02-smoke.sqllogic +62 -0
- package/test/logic/02.1-bind-parameters.sqllogic +223 -0
- package/test/logic/03-expressions.sqllogic +190 -0
- package/test/logic/03.1-quoted-identifiers.sqllogic +55 -0
- package/test/logic/03.4-defaults.sqllogic +554 -0
- package/test/logic/03.4.1-default-edge-cases.sqllogic +99 -0
- package/test/logic/03.5-tvf.sqllogic +61 -0
- package/test/logic/03.6-type-system.sqllogic +544 -0
- package/test/logic/03.6.1-numeric-text-comparison-coercion.sqllogic +244 -0
- package/test/logic/03.6.2-value-to-text.sqllogic +224 -0
- package/test/logic/03.7-bigint-mixed-arithmetic.sqllogic +65 -0
- package/test/logic/03.8-not-precedence.sqllogic +255 -0
- package/test/logic/03.9-is-bool-predicate.sqllogic +150 -0
- package/test/logic/03.9.1-is-bool-predicate-partial-index.sqllogic +34 -0
- package/test/logic/04-order-by.sqllogic +38 -0
- package/test/logic/04-transactions.sqllogic +239 -0
- package/test/logic/04a-savepoint-lazy-attach.sqllogic +122 -0
- package/test/logic/05-vtab_memory.sqllogic +50 -0
- package/test/logic/05.0.1-vtab-memory-unique-index-collation.sqllogic +34 -0
- package/test/logic/05.1-composite-pk-range-scan.sqllogic +147 -0
- package/test/logic/05.1.1-secondary-index-range-scan.sqllogic +56 -0
- package/test/logic/05.2-cast-seek-correctness.sqllogic +124 -0
- package/test/logic/06-builtin_functions.sqllogic +149 -0
- package/test/logic/06.1-string-functions.sqllogic +92 -0
- package/test/logic/06.1.3-like-glob-edges.sqllogic +53 -0
- package/test/logic/06.2-math-functions.sqllogic +82 -0
- package/test/logic/06.3-schema.sqllogic +48 -0
- package/test/logic/06.3.1-schema-all-schemas.sqllogic +39 -0
- package/test/logic/06.3.2-schema-foreign-keys.sqllogic +208 -0
- package/test/logic/06.3.3-introspection-tags.sqllogic +202 -0
- package/test/logic/06.3.3.1-introspection-tags-index.sqllogic +74 -0
- package/test/logic/06.3.4-view-info.sqllogic +378 -0
- package/test/logic/06.3.5-column-info.sqllogic +347 -0
- package/test/logic/06.3.6-schema-views.sqllogic +29 -0
- package/test/logic/06.4-schema-search-path.sqllogic +354 -0
- package/test/logic/06.4.1-schema-case-insensitive.sqllogic +83 -0
- package/test/logic/06.4.2-collation-extras.sqllogic +472 -0
- package/test/logic/06.4.2.1-collation-index-access-paths.sqllogic +140 -0
- package/test/logic/06.4.3-write-path-collation.sqllogic +99 -0
- package/test/logic/06.4.4-comparison-collation-precedence.sqllogic +342 -0
- package/test/logic/06.4.5-any-collate-declared-keys.sqllogic +92 -0
- package/test/logic/06.5-polymorphic-types.sqllogic +169 -0
- package/test/logic/06.5.1-conversion-functions.sqllogic +173 -0
- package/test/logic/06.5.2-scalar-minmax.sqllogic +16 -0
- package/test/logic/06.5.3-undeclared-return-type-comparison.sqllogic +244 -0
- package/test/logic/06.5.4-declared-return-type-builtins.sqllogic +288 -0
- package/test/logic/06.6-aggregate-extended.sqllogic +78 -0
- package/test/logic/06.7-json-extended.sqllogic +53 -0
- package/test/logic/06.8-json-path-operators.sqllogic +99 -0
- package/test/logic/06.9-json-canonical-key.sqllogic +65 -0
- package/test/logic/06.9.1-json-coerce-once.sqllogic +417 -0
- package/test/logic/06.9.1.1-json-coerce-once-index.sqllogic +39 -0
- package/test/logic/06.9.2-json-structural-equality.sqllogic +382 -0
- package/test/logic/06.9.2.1-json-structural-equality-index.sqllogic +76 -0
- package/test/logic/06.9.3-json-index-range-seek.sqllogic +205 -0
- package/test/logic/07-aggregates.sqllogic +193 -0
- package/test/logic/07.1-aggregate-filter-clause.sqllogic +12 -0
- package/test/logic/07.2-aggregate-order-by.sqllogic +11 -0
- package/test/logic/07.3-group-by-extras.sqllogic +602 -0
- package/test/logic/07.3.1-group-by-order-by-key.sqllogic +164 -0
- package/test/logic/07.3.2-grouped-select-list-shape.sqllogic +231 -0
- package/test/logic/07.4-group-by-filter-composite-pk.sqllogic +120 -0
- package/test/logic/07.5-sum-mixed-exact-and-float.sqllogic +151 -0
- package/test/logic/07.5-window.sqllogic +1941 -0
- package/test/logic/07.5.1-window-named.sqllogic +13 -0
- package/test/logic/07.5.2-window-nth-value.sqllogic +13 -0
- package/test/logic/07.6-subqueries.sqllogic +78 -0
- package/test/logic/07.6.1-subquery-extras.sqllogic +93 -0
- package/test/logic/07.7-and-or-short-circuit.sqllogic +58 -0
- package/test/logic/07.7-in-subquery-caching.sqllogic +174 -0
- package/test/logic/07.7-scalar-agg-decorrelation.sqllogic +197 -0
- package/test/logic/07.7.1-in-extras.sqllogic +158 -0
- package/test/logic/07.7.1-scalar-agg-decorrelation-filter.sqllogic +105 -0
- package/test/logic/07.7.2-scalar-agg-decorrelation-nested.sqllogic +115 -0
- package/test/logic/07.7.3-exists-in-select-decorrelation.sqllogic +107 -0
- package/test/logic/07.7.4-where-conjunct-ordering.sqllogic +121 -0
- package/test/logic/07.7.5-filter-lost-under-index-order.sqllogic +134 -0
- package/test/logic/07.7.6-correlated-predicate-scope.sqllogic +178 -0
- package/test/logic/07.7.7-join-source-scope-shadowing.sqllogic +137 -0
- package/test/logic/07.7.8-correlated-ref-to-computed-column.sqllogic +165 -0
- package/test/logic/07.7.9-conjunct-monotonicity.sqllogic +200 -0
- package/test/logic/07.8-correlated-subquery-edges.sqllogic +71 -0
- package/test/logic/07.8.1-correlated-in-decorrelation-gates.sqllogic +156 -0
- package/test/logic/07.9-in-value-list.sqllogic +154 -0
- package/test/logic/07.9.1-in-value-list-composite-index.sqllogic +38 -0
- package/test/logic/08-views.sqllogic +64 -0
- package/test/logic/08.1-semi-anti-join.sqllogic +191 -0
- package/test/logic/08.1-view-edge-cases.sqllogic +118 -0
- package/test/logic/08.1.1-uncorrelated-in-semijoin.sqllogic +251 -0
- package/test/logic/08.2-existence-flag-semijoin-recovery.sqllogic +197 -0
- package/test/logic/08.3-existence-flag-inner-recovery.sqllogic +102 -0
- package/test/logic/08.4-key-set-semi-join.sqllogic +447 -0
- package/test/logic/09-set_operations.sqllogic +244 -0
- package/test/logic/09.1-set-op-cross-collation.sqllogic +230 -0
- package/test/logic/10-distinct_datatypes.sqllogic +127 -0
- package/test/logic/10.1-ddl-lifecycle.sqllogic +203 -0
- package/test/logic/10.1.1-create-table-syntax-edges.sqllogic +47 -0
- package/test/logic/10.1.2-ddl-in-transaction.sqllogic +143 -0
- package/test/logic/10.1.2.1-add-constraint-in-transaction.sqllogic +59 -0
- package/test/logic/10.1.3-ddl-drop-in-transaction.sqllogic +187 -0
- package/test/logic/10.1.3.1-drop-constraint-in-transaction.sqllogic +45 -0
- package/test/logic/10.1.4-create-table-duplicate-constraint-names.sqllogic +149 -0
- package/test/logic/10.1.4-ddl-transaction-policy.sqllogic +314 -0
- package/test/logic/10.1.5-alter-add-constraint-minted-name-disambiguation.sqllogic +219 -0
- package/test/logic/10.1.5-create-index-lifecycle.sqllogic +72 -0
- package/test/logic/10.1.6-ddl-transaction-policy-index.sqllogic +143 -0
- package/test/logic/10.1.7-create-table-default-type-check.sqllogic +119 -0
- package/test/logic/10.1.8-alter-add-check-existing-rows.sqllogic +117 -0
- package/test/logic/10.2-column-features.sqllogic +365 -0
- package/test/logic/10.2.1-table-options-rejection.sqllogic +63 -0
- package/test/logic/10.2.2-default-collation-memory.sqllogic +30 -0
- package/test/logic/10.3-function-features.sqllogic +247 -0
- package/test/logic/10.4-schema-scale.sqllogic +168 -0
- package/test/logic/10.4.1-schema-scale-indexes.sqllogic +121 -0
- package/test/logic/10.5-indexes.sqllogic +164 -0
- package/test/logic/10.5.1-partial-indexes.sqllogic +526 -0
- package/test/logic/10.5.2-expression-indexes.sqllogic +34 -0
- package/test/logic/10.5.3-desc-index-ordering.sqllogic +130 -0
- package/test/logic/10.5.4-composite-pk-index-update-phantom.sqllogic +55 -0
- package/test/logic/10.5.5-index-name-uniqueness.sqllogic +76 -0
- package/test/logic/10.5.6-minmax-index-boundary.sqllogic +204 -0
- package/test/logic/10.5.7-implicit-unique-index-lifecycle.sqllogic +1216 -0
- package/test/logic/10.6-distinct-edge-cases.sqllogic +119 -0
- package/test/logic/100-predicate-normalization-edge-cases.sqllogic +238 -0
- package/test/logic/100.1-where-extras.sqllogic +163 -0
- package/test/logic/100.1.1-where-join-on-order-indexed.sqllogic +35 -0
- package/test/logic/101-builtin-mutation-kills.sqllogic +974 -0
- package/test/logic/101-transaction-edge-cases.sqllogic +395 -0
- package/test/logic/102-schema-catalog-edge-cases.sqllogic +252 -0
- package/test/logic/102-unique-constraints.sqllogic +145 -0
- package/test/logic/102.0.1-schema-catalog-indexed-table.sqllogic +32 -0
- package/test/logic/102.1-unique-edge-cases.sqllogic +111 -0
- package/test/logic/102.1.1-unique-index-creation-edge-cases.sqllogic +75 -0
- package/test/logic/102.2-unique-collation.sqllogic +181 -0
- package/test/logic/102.2.1-unique-collation-index-derived.sqllogic +305 -0
- package/test/logic/103-database-options-edge-cases.sqllogic +108 -0
- package/test/logic/103.1-pragma-boolean-values.sqllogic +126 -0
- package/test/logic/104-emit-mutation-kills.sqllogic +236 -0
- package/test/logic/105-vtab-memory-mutation-kills.sqllogic +754 -0
- package/test/logic/105.1-vtab-memory-index-mutation-kills.sqllogic +540 -0
- package/test/logic/106-constraint-extractor-mutation-kills.sqllogic +350 -0
- package/test/logic/107-temporal-arithmetic-mutation-kills.sqllogic +833 -0
- package/test/logic/108-cardinality-estimation.sqllogic +146 -0
- package/test/logic/108.1-analyze-name-resolution.sqllogic +102 -0
- package/test/logic/109-aggregate-physical-selection.sqllogic +103 -0
- package/test/logic/11-joins.sqllogic +86 -0
- package/test/logic/11.1-join-using.sqllogic +267 -0
- package/test/logic/11.2-comma-join.sqllogic +35 -0
- package/test/logic/11.3-index-nested-loop-join.sqllogic +305 -0
- package/test/logic/11.3-mismatched-collation-join.sqllogic +74 -0
- package/test/logic/11.4-hash-join-side-swap.sqllogic +118 -0
- package/test/logic/110-scan-emitter-mutation-kills.sqllogic +121 -0
- package/test/logic/110.1-scan-emitter-index-scan-kills.sqllogic +47 -0
- package/test/logic/12-empty-primary-key.sqllogic +124 -0
- package/test/logic/12-join_padding_order.sqllogic +58 -0
- package/test/logic/12.1-orderby-index-consumption.sqllogic +40 -0
- package/test/logic/13-cte.sqllogic +137 -0
- package/test/logic/13.1-cte-multiple-recursive.sqllogic +104 -0
- package/test/logic/13.10-cte-qualified-name.sqllogic +136 -0
- package/test/logic/13.11-unreferenced-dml-cte.sqllogic +414 -0
- package/test/logic/13.12-nested-dml-cte-rejected.sqllogic +280 -0
- package/test/logic/13.2-cte-bind-params.sqllogic +98 -0
- package/test/logic/13.3-cte-edge-cases.sqllogic +120 -0
- package/test/logic/13.4-cte-extras.sqllogic +98 -0
- package/test/logic/13.5-cte-join-order.sqllogic +166 -0
- package/test/logic/13.6-cte-dml-runs-once.sqllogic +318 -0
- package/test/logic/13.7-cte-sibling-visibility.sqllogic +227 -0
- package/test/logic/13.8-insert-with-clause-visibility.sqllogic +254 -0
- package/test/logic/13.9-schema-authored-cte-isolation.sqllogic +328 -0
- package/test/logic/13.9.1-schema-authored-schema-path-isolation.sqllogic +408 -0
- package/test/logic/14-utilities.sqllogic +168 -0
- package/test/logic/15-timespan.sqllogic +279 -0
- package/test/logic/15.1-semantic-ordering.sqllogic +960 -0
- package/test/logic/15.1.1-json-check-coercion.sqllogic +178 -0
- package/test/logic/15.1.2-semantic-ordering-index.sqllogic +41 -0
- package/test/logic/15.1.3-declared-form-write-contract.sqllogic +519 -0
- package/test/logic/16-epoch.sqllogic +187 -0
- package/test/logic/17-weekday-modifier.sqllogic +78 -0
- package/test/logic/18-json-string-escapes.sqllogic +86 -0
- package/test/logic/18-strftime-extended-formats.sqllogic +68 -0
- package/test/logic/20-empty-single-row.sqllogic +261 -0
- package/test/logic/21-null-edge-cases.sqllogic +227 -0
- package/test/logic/21.1-where-null-comparisons.sqllogic +151 -0
- package/test/logic/21.1.1-where-null-comparisons-secondary-index.sqllogic +35 -0
- package/test/logic/21.2-between-edges.sqllogic +157 -0
- package/test/logic/22-boundary-values.sqllogic +175 -0
- package/test/logic/23-self-joins-duplicates.sqllogic +74 -0
- package/test/logic/24-builtin-branches.sqllogic +611 -0
- package/test/logic/24.1-substr-extras.sqllogic +75 -0
- package/test/logic/24.2-random-extras.sqllogic +39 -0
- package/test/logic/25-aggregate-edge-cases.sqllogic +289 -0
- package/test/logic/25.1-nested-aggregates.sqllogic +74 -0
- package/test/logic/25.2-having-edge-cases.sqllogic +318 -0
- package/test/logic/25.3-aggregate-isnull-empty.sqllogic +16 -0
- package/test/logic/25.4-groupby-fd-constant-empty.sqllogic +55 -0
- package/test/logic/25.5-ungrouped-aggregate-column-free-select-item.sqllogic +196 -0
- package/test/logic/26-join-edge-cases.sqllogic +333 -0
- package/test/logic/26.1-left-join-isnull-on-notnull.sqllogic +26 -0
- package/test/logic/26.2-left-join-on-vs-where.sqllogic +67 -0
- package/test/logic/26.3-join-on-constants.sqllogic +61 -0
- package/test/logic/26.4-join-on-is-null-nullable.sqllogic +44 -0
- package/test/logic/27-window-edge-cases.sqllogic +206 -0
- package/test/logic/27.1-window-groups-frame.sqllogic +13 -0
- package/test/logic/27.2-window-exclude.sqllogic +13 -0
- package/test/logic/27.3-window-json-aggregation.sqllogic +66 -0
- package/test/logic/28-set-ops-sort-edge-cases.sqllogic +444 -0
- package/test/logic/28.1-compound-limit-offset.sqllogic +143 -0
- package/test/logic/28.2-orderby-expression-extras.sqllogic +698 -0
- package/test/logic/28.2-set-op-branch-types.sqllogic +217 -0
- package/test/logic/29-constraint-edge-cases.sqllogic +442 -0
- package/test/logic/29.1-column-level-conflict-clause.sqllogic +171 -0
- package/test/logic/29.2-table-level-pk-conflict-clause.sqllogic +113 -0
- package/test/logic/40-constraints.sqllogic +418 -0
- package/test/logic/40.1-pk-desc-direction.sqllogic +76 -0
- package/test/logic/40.2-check-extras.sqllogic +683 -0
- package/test/logic/41-alter-table.sqllogic +191 -0
- package/test/logic/41-fk-cascade-conflict-and-self-ref.sqllogic +263 -0
- package/test/logic/41-fk-cross-schema.sqllogic +155 -0
- package/test/logic/41-fk-extended-targets.sqllogic +173 -0
- package/test/logic/41-foreign-keys.sqllogic +1001 -0
- package/test/logic/41-generated-column-errors.sqllogic +225 -0
- package/test/logic/41-generated-column-extras.sqllogic +598 -0
- package/test/logic/41-generated-column-scope.sqllogic +329 -0
- package/test/logic/41-generated-columns.sqllogic +361 -0
- package/test/logic/41.1-alter-pk.sqllogic +230 -0
- package/test/logic/41.1-fk-collation-conflict.sqllogic +277 -0
- package/test/logic/41.10-alter-drop-column-foreign-key.sqllogic +274 -0
- package/test/logic/41.10.1-alter-drop-column-unique-index.sqllogic +169 -0
- package/test/logic/41.10.2-alter-drop-column-check-and-assertion.sqllogic +1616 -0
- package/test/logic/41.10.3-alter-drop-column-referencing-fk.sqllogic +292 -0
- package/test/logic/41.10.4-drop-object-expression-dependents.sqllogic +602 -0
- package/test/logic/41.11-deferred-fk-with-rename.sqllogic +232 -0
- package/test/logic/41.12-alter-default-coercion.sqllogic +200 -0
- package/test/logic/41.13-alter-add-column-generated-backfill.sqllogic +566 -0
- package/test/logic/41.14-alter-add-column-subquery-backfill.sqllogic +129 -0
- package/test/logic/41.15-alter-column-default-type-check.sqllogic +178 -0
- package/test/logic/41.16-fk-unenforceable.sqllogic +364 -0
- package/test/logic/41.2-alter-column.sqllogic +382 -0
- package/test/logic/41.2.1-alter-column-retype-deleted-row-memory.sqllogic +32 -0
- package/test/logic/41.2.2-alter-column-retype-index-rebuild.sqllogic +77 -0
- package/test/logic/41.2.3-alter-column-set-not-null-pk-backfill.sqllogic +310 -0
- package/test/logic/41.2.3.1-alter-column-set-not-null-pk-backfill-secondary-index.sqllogic +37 -0
- package/test/logic/41.3-alter-add-column-unique.sqllogic +184 -0
- package/test/logic/41.3-alter-rename-propagation.sqllogic +2352 -0
- package/test/logic/41.3.1-alter-rename-index-propagation.sqllogic +94 -0
- package/test/logic/41.4-alter-add-column-constraints.sqllogic +855 -0
- package/test/logic/41.5-alter-misc.sqllogic +48 -0
- package/test/logic/41.5-cross-schema-foreign-keys.sqllogic +253 -0
- package/test/logic/41.6-alter-drop-rename-constraint.sqllogic +354 -0
- package/test/logic/41.6.1-alter-drop-constraint-index-derived.sqllogic +22 -0
- package/test/logic/41.7-alter-column-collate.sqllogic +105 -0
- package/test/logic/41.7.1-alter-column-collate-unique.sqllogic +45 -0
- package/test/logic/41.7.2-alter-column-collate-unique-store.sqllogic +208 -0
- package/test/logic/41.7.2.1-alter-column-collate-unique-index-store.sqllogic +114 -0
- package/test/logic/41.7.3-alter-column-retype-unique.sqllogic +293 -0
- package/test/logic/41.7.3.1-alter-column-retype-staged-rows-memory.sqllogic +41 -0
- package/test/logic/41.7.3.2-alter-column-retype-unique-index.sqllogic +72 -0
- package/test/logic/41.7.4-alter-column-retype-semantic-memory.sqllogic +445 -0
- package/test/logic/41.7.4.1-alter-column-retype-semantic-index-memory.sqllogic +91 -0
- package/test/logic/41.7.5-alter-column-collate-pk-staged-delete-memory.sqllogic +106 -0
- package/test/logic/41.8-alter-add-constraint-unique-fk.sqllogic +275 -0
- package/test/logic/41.8-alter-savepoint-staged-rows.sqllogic +443 -0
- package/test/logic/41.8.1-alter-rename-savepoint-staged-rows.sqllogic +408 -0
- package/test/logic/41.9-alter-constraint-body-change.sqllogic +180 -0
- package/test/logic/41.9-fk-restrict-batched.sqllogic +290 -0
- package/test/logic/42-committed-snapshot.sqllogic +254 -0
- package/test/logic/42-returning.sqllogic +159 -0
- package/test/logic/42.1-returning-extras.sqllogic +232 -0
- package/test/logic/42.2-returning-stored-row.sqllogic +165 -0
- package/test/logic/43-default-nullability.sqllogic +131 -0
- package/test/logic/43-transition-constraints.sqllogic +341 -0
- package/test/logic/43.1-default-collation.sqllogic +140 -0
- package/test/logic/43.1-notnull-or-conflict.sqllogic +79 -0
- package/test/logic/43.2-deferred-check-new-on-delete.sqllogic +95 -0
- package/test/logic/43.3-nullable-primary-key.sqllogic +587 -0
- package/test/logic/44-determinism-validation.sqllogic +164 -0
- package/test/logic/44-orthogonality.sqllogic +146 -0
- package/test/logic/44.1-nondeterministic-schema.sqllogic +316 -0
- package/test/logic/45-udf-determinism.sqllogic +39 -0
- package/test/logic/46-mutation-context.sqllogic +595 -0
- package/test/logic/47-upsert.sqllogic +255 -0
- package/test/logic/47.1-upsert-conflict-targets.sqllogic +169 -0
- package/test/logic/47.2-replace-and-or-clauses.sqllogic +164 -0
- package/test/logic/47.2.1-pk-replace-secondary-unique.sqllogic +374 -0
- package/test/logic/47.3-engine-or-fail.sqllogic +122 -0
- package/test/logic/47.3-upsert-conflict-target-collation.sqllogic +139 -0
- package/test/logic/47.3.1-upsert-conflict-index-derived-collation.sqllogic +25 -0
- package/test/logic/47.4-upsert-conflict-target-affinity.sqllogic +59 -0
- package/test/logic/47.5-upsert-do-update-constraints.sqllogic +440 -0
- package/test/logic/47.6-upsert-do-update-decomposed.sqllogic +100 -0
- package/test/logic/49-reference-graph.sqllogic +84 -0
- package/test/logic/50-declarative-schema.sqllogic +2550 -0
- package/test/logic/50-metadata-tags.sqllogic +1033 -0
- package/test/logic/50.0.1-metadata-tags-index.sqllogic +141 -0
- package/test/logic/50.1-declare-schema-pk.sqllogic +150 -0
- package/test/logic/50.2-declare-schema-renames.sqllogic +949 -0
- package/test/logic/50.2.1-declare-schema-index-renames.sqllogic +173 -0
- package/test/logic/50.3-declare-schema-duplicate-names.sqllogic +280 -0
- package/test/logic/50.4-declare-schema-apply-restore.sqllogic +192 -0
- package/test/logic/51-lens-foundation.sqllogic +147 -0
- package/test/logic/51-materialized-views.sqllogic +376 -0
- package/test/logic/51.5-materialized-views-coarsened-key.sqllogic +198 -0
- package/test/logic/51.7-maintained-table-attach-detach.sqllogic +468 -0
- package/test/logic/51.8-maintained-table-declared-constraints.sqllogic +440 -0
- package/test/logic/51.85-maintained-table-parent-fk.sqllogic +103 -0
- package/test/logic/51.9-maintained-table-secondary-unique.sqllogic +237 -0
- package/test/logic/51.9.1-maintained-table-partial-unique.sqllogic +36 -0
- package/test/logic/52-lens-overrides.sqllogic +566 -0
- package/test/logic/53-materialized-views-rowtime.sqllogic +1601 -0
- package/test/logic/53-reserved-tags.sqllogic +252 -0
- package/test/logic/53.1-materialized-view-write-through.sqllogic +329 -0
- package/test/logic/53.2-materialized-view-rename-propagation.sqllogic +483 -0
- package/test/logic/53.3-materialized-view-constraint-only-ddl.sqllogic +409 -0
- package/test/logic/53.3.1-materialized-view-index-ddl-irrelevance.sqllogic +29 -0
- package/test/logic/53.4-materialized-view-structural-alter-restore.sqllogic +436 -0
- package/test/logic/54-covering-mv-enforcement.sqllogic +314 -0
- package/test/logic/54.1-covering-mv-partial-enforcement.sqllogic +34 -0
- package/test/logic/55-internal-eviction-reporting.sqllogic +89 -0
- package/test/logic/55-lens-prover.sqllogic +202 -0
- package/test/logic/55.1-eviction-restrict-fk.sqllogic +148 -0
- package/test/logic/55.5-lens-authored-inverse.sqllogic +1468 -0
- package/test/logic/80-grow-retrieve-pass.sqllogic +79 -0
- package/test/logic/81-quickpick.sqllogic +24 -0
- package/test/logic/82-bloom-join.sqllogic +185 -0
- package/test/logic/83-merge-join.sqllogic +280 -0
- package/test/logic/84-asof-scan.sqllogic +238 -0
- package/test/logic/84-key-cardinality.sqllogic +50 -0
- package/test/logic/85-relational-const-folding.sqllogic +51 -0
- package/test/logic/86-scalar-cse.sqllogic +42 -0
- package/test/logic/90-error_paths.sqllogic +114 -0
- package/test/logic/90.1-parse-errors.sqllogic +77 -0
- package/test/logic/90.2-alter-table-errors.sqllogic +53 -0
- package/test/logic/90.2.1-alter-extra-errors.sqllogic +51 -0
- package/test/logic/90.3-expression-errors.sqllogic +43 -0
- package/test/logic/90.4-dml-errors.sqllogic +47 -0
- package/test/logic/90.5-unsupported-join-types.sqllogic +37 -0
- package/test/logic/90.5.1-right-full-join-read.sqllogic +157 -0
- package/test/logic/90.6-select-error-paths.sqllogic +57 -0
- package/test/logic/91-merge-join-edge-cases.sqllogic +159 -0
- package/test/logic/92-hash-aggregate-edge-cases.sqllogic +139 -0
- package/test/logic/93-ddl-view-edge-cases.sqllogic +104 -0
- package/test/logic/93.1-view-error-paths.sqllogic +74 -0
- package/test/logic/93.2-view-mutation-pending.sqllogic +108 -0
- package/test/logic/93.3-view-mutation-or-clause.sqllogic +60 -0
- package/test/logic/93.4-view-mutation.sqllogic +4881 -0
- package/test/logic/93.4.1-view-mutation-partial-unique-cardinality.sqllogic +31 -0
- package/test/logic/93.5-authored-inverse.sqllogic +415 -0
- package/test/logic/93.6-set-op-flagless-write.sqllogic +862 -0
- package/test/logic/94-tvf-edge-cases.sqllogic +58 -0
- package/test/logic/94.1-limit-offset-edge-cases.sqllogic +109 -0
- package/test/logic/94.2-limit-offset-extras.sqllogic +98 -0
- package/test/logic/95-assertions.sqllogic +1417 -0
- package/test/logic/96-subquery-edge-cases.sqllogic +276 -0
- package/test/logic/97-json-function-edge-cases.sqllogic +633 -0
- package/test/logic/97.1-json-blob-and-special-values.sqllogic +92 -0
- package/test/logic/98-temporal-edge-cases.sqllogic +515 -0
- package/test/logic/99-conversion-edge-cases.sqllogic +325 -0
- package/test/logic/99.0.1-schema-index-listing.sqllogic +21 -0
- package/test/logic/99.1-cast-syntax-extras.sqllogic +167 -0
- package/test/logic/drop-unique-index.sqllogic +88 -0
- package/test/logic/pushdown-test.sqllogic +36 -0
|
@@ -0,0 +1,1601 @@
|
|
|
1
|
+
-- 53-materialized-views-rowtime.sqllogic — row-time (write-through) materialized
|
|
2
|
+
-- views. Every materialized view is row-time maintained: the covering-index-shaped
|
|
3
|
+
-- backing table is kept consistent SYNCHRONOUSLY with each source row-write, within
|
|
4
|
+
-- the same transaction and visible mid-statement (reads-own-writes), and rolled back
|
|
5
|
+
-- in lockstep with the source write. Observable here purely by reading the MV (which
|
|
6
|
+
-- resolves to its backing table); row-time UNIQUE enforcement is covered separately
|
|
7
|
+
-- in 54-covering-mv-enforcement.sqllogic.
|
|
8
|
+
|
|
9
|
+
-- ===================================
|
|
10
|
+
-- 1. Autocommit write-through — insert / update / delete reflected with no BEGIN
|
|
11
|
+
-- and no manual refresh (maintenance rides the statement-level autocommit).
|
|
12
|
+
-- ===================================
|
|
13
|
+
|
|
14
|
+
create table rt (id integer primary key, x integer);
|
|
15
|
+
insert into rt values (1, 10), (2, 20);
|
|
16
|
+
create materialized view rt_ix as select x, id from rt order by x;
|
|
17
|
+
|
|
18
|
+
-- MV equals the body at create time
|
|
19
|
+
select * from rt_ix order by x;
|
|
20
|
+
→ [{"x":10,"id":1},{"x":20,"id":2}]
|
|
21
|
+
|
|
22
|
+
-- autocommit INSERT is reflected immediately (no refresh)
|
|
23
|
+
insert into rt values (3, 5);
|
|
24
|
+
select * from rt_ix order by x;
|
|
25
|
+
→ [{"x":5,"id":3},{"x":10,"id":1},{"x":20,"id":2}]
|
|
26
|
+
|
|
27
|
+
-- autocommit UPDATE of a projected column moves the backing row's key
|
|
28
|
+
update rt set x = 100 where id = 2;
|
|
29
|
+
select * from rt_ix order by x;
|
|
30
|
+
→ [{"x":5,"id":3},{"x":10,"id":1},{"x":100,"id":2}]
|
|
31
|
+
|
|
32
|
+
-- autocommit DELETE removes the backing row
|
|
33
|
+
delete from rt where id = 1;
|
|
34
|
+
select * from rt_ix order by x;
|
|
35
|
+
→ [{"x":5,"id":3},{"x":100,"id":2}]
|
|
36
|
+
|
|
37
|
+
-- ===================================
|
|
38
|
+
-- 2. Mid-transaction visibility + rollback reverts. The hallmark of row-time:
|
|
39
|
+
-- a `select` from the MV inside an open transaction reflects the just-written
|
|
40
|
+
-- source row BEFORE commit, and rollback discards the backing delta with the
|
|
41
|
+
-- source write.
|
|
42
|
+
-- ===================================
|
|
43
|
+
|
|
44
|
+
begin;
|
|
45
|
+
insert into rt values (4, 7);
|
|
46
|
+
-- reflected mid-transaction, before commit
|
|
47
|
+
select * from rt_ix order by x;
|
|
48
|
+
→ [{"x":5,"id":3},{"x":7,"id":4},{"x":100,"id":2}]
|
|
49
|
+
rollback;
|
|
50
|
+
-- backing delta discarded alongside the rolled-back source write
|
|
51
|
+
select * from rt_ix order by x;
|
|
52
|
+
→ [{"x":5,"id":3},{"x":100,"id":2}]
|
|
53
|
+
|
|
54
|
+
-- A committed transaction persists the write-through delta.
|
|
55
|
+
begin;
|
|
56
|
+
insert into rt values (5, 50);
|
|
57
|
+
commit;
|
|
58
|
+
select * from rt_ix order by x;
|
|
59
|
+
→ [{"x":5,"id":3},{"x":50,"id":5},{"x":100,"id":2}]
|
|
60
|
+
|
|
61
|
+
-- A FAILED source write leaves no backing delta: a multi-row autocommit INSERT
|
|
62
|
+
-- whose second row violates the source PK aborts the whole statement, reverting
|
|
63
|
+
-- both the first row's source write and its backing maintenance (atomic via the
|
|
64
|
+
-- statement savepoint the maintenance connection rides).
|
|
65
|
+
insert into rt values (6, 60), (6, 61);
|
|
66
|
+
-- error: constraint failed
|
|
67
|
+
select * from rt_ix order by x;
|
|
68
|
+
→ [{"x":5,"id":3},{"x":50,"id":5},{"x":100,"id":2}]
|
|
69
|
+
|
|
70
|
+
-- A FAILED multi-row UPDATE inside an explicit transaction is atomic at the
|
|
71
|
+
-- statement level: the first row's source write AND its row-time backing
|
|
72
|
+
-- maintenance are both reverted when a later row trips a source constraint.
|
|
73
|
+
-- (The statement savepoint that wraps the source UPDATE also unwinds the
|
|
74
|
+
-- backing connection, which rides the same savepoint stack.)
|
|
75
|
+
create table rta (id integer primary key, x integer not null);
|
|
76
|
+
insert into rta values (1, 10), (2, 20), (3, 30);
|
|
77
|
+
create materialized view rta_ix as select x, id from rta order by x;
|
|
78
|
+
select * from rta_ix order by x;
|
|
79
|
+
→ [{"x":10,"id":1},{"x":20,"id":2},{"x":30,"id":3}]
|
|
80
|
+
|
|
81
|
+
begin;
|
|
82
|
+
-- id=1 updates to x=11 (and maintains the backing), then id=2 violates NOT NULL.
|
|
83
|
+
update rta set x = case when id = 2 then null else x + 1 end where id in (1, 2);
|
|
84
|
+
-- error: NOT NULL constraint failed: rta.x
|
|
85
|
+
|
|
86
|
+
-- Neither the source nor the MV backing retains id=1's x=11 mid-transaction.
|
|
87
|
+
select x from rta where id = 1;
|
|
88
|
+
→ [{"x":10}]
|
|
89
|
+
select * from rta_ix order by x;
|
|
90
|
+
→ [{"x":10,"id":1},{"x":20,"id":2},{"x":30,"id":3}]
|
|
91
|
+
rollback;
|
|
92
|
+
|
|
93
|
+
-- And nothing persisted after rollback either.
|
|
94
|
+
select * from rta_ix order by x;
|
|
95
|
+
→ [{"x":10,"id":1},{"x":20,"id":2},{"x":30,"id":3}]
|
|
96
|
+
|
|
97
|
+
-- ===================================
|
|
98
|
+
-- 3. Partial body (WHERE) — scope transitions. A row contributes a backing row
|
|
99
|
+
-- only while its predicate is unambiguously TRUE; an UPDATE across the
|
|
100
|
+
-- predicate boundary deletes / inserts the backing row.
|
|
101
|
+
-- ===================================
|
|
102
|
+
|
|
103
|
+
create table rtf (id integer primary key, x integer);
|
|
104
|
+
insert into rtf values (1, 5), (2, -3), (3, 8);
|
|
105
|
+
create materialized view rtf_ix as select x, id from rtf where x > 0 order by x;
|
|
106
|
+
select * from rtf_ix order by x;
|
|
107
|
+
→ [{"x":5,"id":1},{"x":8,"id":3}]
|
|
108
|
+
|
|
109
|
+
-- id=1 leaves scope (x>0 → x=-1): its backing row disappears
|
|
110
|
+
update rtf set x = -1 where id = 1;
|
|
111
|
+
select * from rtf_ix order by x;
|
|
112
|
+
→ [{"x":8,"id":3}]
|
|
113
|
+
|
|
114
|
+
-- id=2 enters scope (x=-3 → x=10): its backing row appears
|
|
115
|
+
update rtf set x = 10 where id = 2;
|
|
116
|
+
select * from rtf_ix order by x;
|
|
117
|
+
→ [{"x":8,"id":3},{"x":10,"id":2}]
|
|
118
|
+
|
|
119
|
+
-- in-scope key-changing update (projected column changes): old key deleted, new upserted
|
|
120
|
+
update rtf set x = 9 where id = 3;
|
|
121
|
+
select * from rtf_ix order by x;
|
|
122
|
+
→ [{"x":9,"id":3},{"x":10,"id":2}]
|
|
123
|
+
|
|
124
|
+
-- delete of an in-scope row removes its backing row
|
|
125
|
+
delete from rtf where id = 2;
|
|
126
|
+
select * from rtf_ix order by x;
|
|
127
|
+
→ [{"x":9,"id":3}]
|
|
128
|
+
|
|
129
|
+
-- ===================================
|
|
130
|
+
-- 4. Multi-row statement reads-own-writes — every row's maintenance accumulates
|
|
131
|
+
-- in the same backing pending layer; a multi-row INSERT yields the full set.
|
|
132
|
+
-- (For the covering-index shape the backing key carries the source PK, so two
|
|
133
|
+
-- distinct source rows never collide on the backing key; this verifies
|
|
134
|
+
-- pending-layer accumulation, the read-your-own-writes substrate enforcement
|
|
135
|
+
-- will rely on.)
|
|
136
|
+
-- ===================================
|
|
137
|
+
|
|
138
|
+
create table m (id integer primary key, x integer);
|
|
139
|
+
create materialized view m_ix as select x, id from m order by x, id;
|
|
140
|
+
insert into m values (1, 5), (2, 5), (3, 7);
|
|
141
|
+
select * from m_ix order by x, id;
|
|
142
|
+
→ [{"x":5,"id":1},{"x":5,"id":2},{"x":7,"id":3}]
|
|
143
|
+
|
|
144
|
+
-- ===================================
|
|
145
|
+
-- 5. Compound-PK source — backing key carries both PK columns.
|
|
146
|
+
-- ===================================
|
|
147
|
+
|
|
148
|
+
create table cp (a integer, b integer, v integer, primary key (a, b));
|
|
149
|
+
insert into cp values (1, 1, 100), (1, 2, 200);
|
|
150
|
+
create materialized view cp_ix as select a, b, v from cp;
|
|
151
|
+
select * from cp_ix order by a, b;
|
|
152
|
+
→ [{"a":1,"b":1,"v":100},{"a":1,"b":2,"v":200}]
|
|
153
|
+
insert into cp values (2, 1, 300);
|
|
154
|
+
select * from cp_ix order by a, b;
|
|
155
|
+
→ [{"a":1,"b":1,"v":100},{"a":1,"b":2,"v":200},{"a":2,"b":1,"v":300}]
|
|
156
|
+
update cp set v = 999 where a = 1 and b = 1;
|
|
157
|
+
select * from cp_ix order by a, b;
|
|
158
|
+
→ [{"a":1,"b":1,"v":999},{"a":1,"b":2,"v":200},{"a":2,"b":1,"v":300}]
|
|
159
|
+
|
|
160
|
+
-- ===================================
|
|
161
|
+
-- 6. Two row-time MVs over one source — both maintained from a single write.
|
|
162
|
+
-- ===================================
|
|
163
|
+
|
|
164
|
+
create table s (id integer primary key, x integer);
|
|
165
|
+
insert into s values (1, 1);
|
|
166
|
+
create materialized view s_p as select id, x from s;
|
|
167
|
+
create materialized view s_o as select x, id from s order by x;
|
|
168
|
+
insert into s values (2, 2);
|
|
169
|
+
select * from s_p order by id;
|
|
170
|
+
→ [{"id":1,"x":1},{"id":2,"x":2}]
|
|
171
|
+
select * from s_o order by x;
|
|
172
|
+
→ [{"x":1,"id":1},{"x":2,"id":2}]
|
|
173
|
+
|
|
174
|
+
-- ===================================
|
|
175
|
+
-- 7. Cost-gated-with-floor create gate. **No body is rejected for its SHAPE** — a shape
|
|
176
|
+
-- that fits no bounded-delta arm is maintained by the always-correct full-rebuild floor
|
|
177
|
+
-- (re-evaluate the whole body, replace the backing). Only four NON-shape rejections
|
|
178
|
+
-- remain: non-determinism, a bag (no provable unique key), no relational output, and a
|
|
179
|
+
-- full-rebuild-only body over a source past the size threshold (the size reject needs
|
|
180
|
+
-- StatsProvider counts, and the threshold pragma / its 0-disables behavior / invalid-value
|
|
181
|
+
-- rejection are all covered in materialized-view-diagnostics.spec.ts). This section pins the
|
|
182
|
+
-- remaining rejects (distinctive tails) AND the shape ACCEPTANCES the flip introduced; §26
|
|
183
|
+
-- below then exercises full-rebuild-floor maintenance end-to-end under SQL writes.
|
|
184
|
+
-- ===================================
|
|
185
|
+
|
|
186
|
+
create table g (id integer primary key, k integer, v integer);
|
|
187
|
+
insert into g values (1, 100, 5);
|
|
188
|
+
create table g2 (id integer primary key, w integer);
|
|
189
|
+
|
|
190
|
+
-- ── KEPT rejects (all non-shape) ──
|
|
191
|
+
|
|
192
|
+
-- non-deterministic projected expression — a reproducibility reject (the inverse-projection
|
|
193
|
+
-- arm's arm-specific tail), NOT a shape reject.
|
|
194
|
+
create materialized view bad_nondet as select id, random() as r from g;
|
|
195
|
+
-- error: non-deterministic expression column 'r'
|
|
196
|
+
|
|
197
|
+
-- a projection dropping the source PK leaves a non-keyed (bag) body — the floor's
|
|
198
|
+
-- no-provable-unique-key reject (the single create row was distinct, so it reached the gate
|
|
199
|
+
-- rather than failing the set contract at fill).
|
|
200
|
+
create materialized view bad_nopk as select v from g;
|
|
201
|
+
-- error: no provable unique key
|
|
202
|
+
|
|
203
|
+
-- a recursive-CTE `union all` body is a bag (overlapping inputs, no provable key) — floor reject.
|
|
204
|
+
create materialized view bad_rec as with recursive r(n) as (select 1 union all select n + 1 from r where n < 3) select n from r;
|
|
205
|
+
-- error: no provable unique key
|
|
206
|
+
|
|
207
|
+
-- ── FLIPPED acceptances (shape is no longer a reject) ──
|
|
208
|
+
-- Each CREATEs and reads its snapshot; the floor (or a bounded-delta arm) maintains it.
|
|
209
|
+
-- Deep maintenance equivalence is the property harness's job (maintenance-equivalence.spec.ts
|
|
210
|
+
-- § full-rebuild floor); §26 below exercises floor maintenance under writes.
|
|
211
|
+
|
|
212
|
+
-- DISTINCT (single source) — a set, maintained by the full-rebuild floor.
|
|
213
|
+
create materialized view ok_distinct as select distinct v from g;
|
|
214
|
+
select v from ok_distinct order by v;
|
|
215
|
+
→ [{"v":5}]
|
|
216
|
+
|
|
217
|
+
-- scalar aggregate (no GROUP BY) — one global row, full-rebuild floor.
|
|
218
|
+
create materialized view ok_scalar as select count(*) as c, sum(v) as s from g;
|
|
219
|
+
select c, s from ok_scalar;
|
|
220
|
+
→ [{"c":1,"s":5}]
|
|
221
|
+
|
|
222
|
+
-- UNION over two tables — a set, full-rebuild floor.
|
|
223
|
+
create materialized view ok_union as select id, v from g union select id, w from g2;
|
|
224
|
+
select id, v from ok_union order by id;
|
|
225
|
+
→ [{"id":1,"v":5}]
|
|
226
|
+
|
|
227
|
+
-- a WHERE-subquery over a second table — full-rebuild floor.
|
|
228
|
+
create materialized view ok_wheresubq as select id, v from g where v in (select w from g2);
|
|
229
|
+
select id, v from ok_wheresubq order by id;
|
|
230
|
+
→ []
|
|
231
|
+
|
|
232
|
+
-- LIMIT — full-rebuild floor.
|
|
233
|
+
create materialized view ok_limit as select id, v from g limit 10;
|
|
234
|
+
select id, v from ok_limit order by id;
|
|
235
|
+
→ [{"id":1,"v":5}]
|
|
236
|
+
|
|
237
|
+
-- a correlated scalar-subquery projection column — full-rebuild floor.
|
|
238
|
+
create materialized view ok_subq as select id, (select g.v) as vv from g;
|
|
239
|
+
select id, vv from ok_subq order by id;
|
|
240
|
+
→ [{"id":1,"vv":5}]
|
|
241
|
+
|
|
242
|
+
-- order-by aggregate (the order-by makes a non-group-key backing column, so the
|
|
243
|
+
-- residual-recompute arm declines) — full-rebuild floor.
|
|
244
|
+
create materialized view ok_obagg as select k, sum(v) as s from g group by k order by sum(v);
|
|
245
|
+
select k, s from ok_obagg order by k;
|
|
246
|
+
→ [{"k":100,"s":5}]
|
|
247
|
+
|
|
248
|
+
-- a lateral-TVF fan-out whose `order by` reorders the composite key so the base PK no longer
|
|
249
|
+
-- leads it (the prefix-delete arm declines) — full-rebuild floor.
|
|
250
|
+
create materialized view ok_lat_order as select g.id, f.value from g cross join lateral generate_series(1, g.v) f order by f.value;
|
|
251
|
+
select id, value from ok_lat_order order by id, value;
|
|
252
|
+
→ [{"id":1,"value":1},{"id":1,"value":2},{"id":1,"value":3},{"id":1,"value":4},{"id":1,"value":5}]
|
|
253
|
+
|
|
254
|
+
-- a provably-1:1 FK join WITH a partial WHERE — now a bounded-delta join-residual arm
|
|
255
|
+
-- (mv-join-where-widening), not the floor. This `where jwp.name > 'm'` references the lookup
|
|
256
|
+
-- jwp, so the lookup side is delete-capable; §24.5 exercises the membership flips under writes.
|
|
257
|
+
create table jwp (id integer primary key, name text);
|
|
258
|
+
create table jwt (id integer primary key, fk integer not null references jwp(id));
|
|
259
|
+
insert into jwp values (1, 'alice'), (2, 'zoe');
|
|
260
|
+
insert into jwt values (10, 1), (20, 2);
|
|
261
|
+
create materialized view ok_join_where as select jwt.id, jwp.name from jwt join jwp on jwt.fk = jwp.id where jwp.name > 'm';
|
|
262
|
+
select id, name from ok_join_where order by id;
|
|
263
|
+
→ [{"id":20,"name":"zoe"}]
|
|
264
|
+
|
|
265
|
+
-- the same FK join as an OUTER (left) join (the join-residual arm declines an outer join) —
|
|
266
|
+
-- still keyed on the driving PK: full-rebuild floor.
|
|
267
|
+
create materialized view ok_join_outer as select jwt.id, jwp.name from jwt left join jwp on jwt.fk = jwp.id;
|
|
268
|
+
select id, name from ok_join_outer order by id;
|
|
269
|
+
→ [{"id":10,"name":"alice"},{"id":20,"name":"zoe"}]
|
|
270
|
+
|
|
271
|
+
-- NOTE: a *fanning* (non-1:1) join such as `select g.id, g.v from g join g2 on g.k = g2.w`
|
|
272
|
+
-- now also routes to the floor and is ACCEPTED at create, but the floor trusts the optimizer's
|
|
273
|
+
-- set-inference (`keysOf`); when such a body actually fans out it can produce duplicates the
|
|
274
|
+
-- backing key collapses, diverging from the plain view. That `isSet`/`keysOf` over-claim is a
|
|
275
|
+
-- pre-existing optimizer gap the flip exposes — filed as a fix ticket — so it is deliberately
|
|
276
|
+
-- NOT asserted here.
|
|
277
|
+
|
|
278
|
+
-- ===================================
|
|
279
|
+
-- 8. MV-over-MV cascade. A materialized view whose source is itself another MV's
|
|
280
|
+
-- backing table is accepted: `building/select.ts` rewrites the inner-MV reference
|
|
281
|
+
-- to its backing table, so the outer MV's source base IS the inner MV's backing
|
|
282
|
+
-- base and the existing `rowTimeBySource[backingBase]` index records the
|
|
283
|
+
-- producer→consumer edge. A source write maintains the inner MV's backing, whose
|
|
284
|
+
-- effective per-row change is routed onward to the outer MV — recursively,
|
|
285
|
+
-- synchronously, DAG-ordered, atomic within the statement.
|
|
286
|
+
-- ===================================
|
|
287
|
+
|
|
288
|
+
-- 2-level chain c2 over c1 over c. insert / update / delete reflect in BOTH levels
|
|
289
|
+
-- with no refresh.
|
|
290
|
+
create table c (id integer primary key, x integer);
|
|
291
|
+
insert into c values (1, 10), (2, 20);
|
|
292
|
+
create materialized view c1 as select id, x from c;
|
|
293
|
+
create materialized view c2 as select id, x from c1;
|
|
294
|
+
select id, x from c1 order by id;
|
|
295
|
+
→ [{"id":1,"x":10},{"id":2,"x":20}]
|
|
296
|
+
select id, x from c2 order by id;
|
|
297
|
+
→ [{"id":1,"x":10},{"id":2,"x":20}]
|
|
298
|
+
|
|
299
|
+
-- insert cascades to both levels
|
|
300
|
+
insert into c values (3, 30);
|
|
301
|
+
select id, x from c2 order by id;
|
|
302
|
+
→ [{"id":1,"x":10},{"id":2,"x":20},{"id":3,"x":30}]
|
|
303
|
+
|
|
304
|
+
-- update of a projected column cascades to both levels (delete-old/upsert-new at each)
|
|
305
|
+
update c set x = 99 where id = 2;
|
|
306
|
+
select id, x from c1 order by id;
|
|
307
|
+
→ [{"id":1,"x":10},{"id":2,"x":99},{"id":3,"x":30}]
|
|
308
|
+
select id, x from c2 order by id;
|
|
309
|
+
→ [{"id":1,"x":10},{"id":2,"x":99},{"id":3,"x":30}]
|
|
310
|
+
|
|
311
|
+
-- delete cascades to both levels
|
|
312
|
+
delete from c where id = 1;
|
|
313
|
+
select id, x from c2 order by id;
|
|
314
|
+
→ [{"id":2,"x":99},{"id":3,"x":30}]
|
|
315
|
+
|
|
316
|
+
-- Predicate transition through the chain. The inner MV filters x>0; the outer is a
|
|
317
|
+
-- plain passthrough of the inner. An update moving a row out of the inner predicate
|
|
318
|
+
-- removes it from BOTH levels; the reverse re-adds it to both.
|
|
319
|
+
create table pc (id integer primary key, x integer);
|
|
320
|
+
insert into pc values (1, 5), (2, 7);
|
|
321
|
+
create materialized view pc1 as select id, x from pc where x > 0;
|
|
322
|
+
create materialized view pc2 as select id, x from pc1;
|
|
323
|
+
select id, x from pc2 order by id;
|
|
324
|
+
→ [{"id":1,"x":5},{"id":2,"x":7}]
|
|
325
|
+
|
|
326
|
+
-- id=1 leaves the inner scope (x>0 → x=-1) → removed at level 2 as well
|
|
327
|
+
update pc set x = -1 where id = 1;
|
|
328
|
+
select id, x from pc1 order by id;
|
|
329
|
+
→ [{"id":2,"x":7}]
|
|
330
|
+
select id, x from pc2 order by id;
|
|
331
|
+
→ [{"id":2,"x":7}]
|
|
332
|
+
|
|
333
|
+
-- reverse: id=1 re-enters the inner scope → re-added to both
|
|
334
|
+
update pc set x = 5 where id = 1;
|
|
335
|
+
select id, x from pc2 order by id;
|
|
336
|
+
→ [{"id":1,"x":5},{"id":2,"x":7}]
|
|
337
|
+
|
|
338
|
+
-- 3-level chain proves arbitrary depth and DAG order (each level fully written
|
|
339
|
+
-- before its consumer runs).
|
|
340
|
+
create table d (id integer primary key, x integer);
|
|
341
|
+
create materialized view d1 as select id, x from d;
|
|
342
|
+
create materialized view d2 as select id, x from d1;
|
|
343
|
+
create materialized view d3 as select id, x from d2;
|
|
344
|
+
insert into d values (1, 100);
|
|
345
|
+
select id, x from d3 order by id;
|
|
346
|
+
→ [{"id":1,"x":100}]
|
|
347
|
+
update d set x = 200 where id = 1;
|
|
348
|
+
select id, x from d3 order by id;
|
|
349
|
+
→ [{"id":1,"x":200}]
|
|
350
|
+
delete from d where id = 1;
|
|
351
|
+
select id, x from d3 order by id;
|
|
352
|
+
→ []
|
|
353
|
+
|
|
354
|
+
-- Fan-out at a cascade backing: one inner MV (`fo1`) feeds TWO outer MVs with
|
|
355
|
+
-- distinct backing keys. A single source write maintains the inner backing once,
|
|
356
|
+
-- whose effective per-row change is routed to BOTH dependents (the manager loops
|
|
357
|
+
-- over every MV reading that backing base, not just the first).
|
|
358
|
+
create table fo (id integer primary key, x integer);
|
|
359
|
+
create materialized view fo1 as select id, x from fo;
|
|
360
|
+
create materialized view fo2a as select id, x from fo1;
|
|
361
|
+
create materialized view fo2b as select x, id from fo1 order by x;
|
|
362
|
+
insert into fo values (1, 10), (2, 20);
|
|
363
|
+
select id, x from fo2a order by id;
|
|
364
|
+
→ [{"id":1,"x":10},{"id":2,"x":20}]
|
|
365
|
+
select x, id from fo2b order by x;
|
|
366
|
+
→ [{"x":10,"id":1},{"x":20,"id":2}]
|
|
367
|
+
|
|
368
|
+
-- update cascades to BOTH fan-out branches (each re-keys its own backing)
|
|
369
|
+
update fo set x = 99 where id = 1;
|
|
370
|
+
select id, x from fo2a order by id;
|
|
371
|
+
→ [{"id":1,"x":99},{"id":2,"x":20}]
|
|
372
|
+
select x, id from fo2b order by x;
|
|
373
|
+
→ [{"x":20,"id":2},{"x":99,"id":1}]
|
|
374
|
+
|
|
375
|
+
-- delete cascades to both branches
|
|
376
|
+
delete from fo where id = 2;
|
|
377
|
+
select id, x from fo2a order by id;
|
|
378
|
+
→ [{"id":1,"x":99}]
|
|
379
|
+
select x, id from fo2b order by x;
|
|
380
|
+
→ [{"x":99,"id":1}]
|
|
381
|
+
|
|
382
|
+
-- Mid-transaction visibility through the chain + whole-chain atomic rollback. The
|
|
383
|
+
-- new row is visible at the deepest level before commit (reads-own-writes rides the
|
|
384
|
+
-- per-statement backing connections), and rollback reverts every level in lockstep.
|
|
385
|
+
create table tx (id integer primary key, x integer);
|
|
386
|
+
create materialized view tx1 as select id, x from tx;
|
|
387
|
+
create materialized view tx2 as select id, x from tx1;
|
|
388
|
+
insert into tx values (1, 1);
|
|
389
|
+
|
|
390
|
+
begin;
|
|
391
|
+
insert into tx values (2, 2);
|
|
392
|
+
select id, x from tx2 order by id;
|
|
393
|
+
→ [{"id":1,"x":1},{"id":2,"x":2}]
|
|
394
|
+
rollback;
|
|
395
|
+
-- both levels reverted together
|
|
396
|
+
select id, x from tx2 order by id;
|
|
397
|
+
→ [{"id":1,"x":1}]
|
|
398
|
+
select id, x from tx1 order by id;
|
|
399
|
+
→ [{"id":1,"x":1}]
|
|
400
|
+
|
|
401
|
+
-- ===================================
|
|
402
|
+
-- 9. DROP detaches the row-time plan — the dropped name stops resolving, a source
|
|
403
|
+
-- write touches no detached plan, and a freshly-created row-time MV on the same
|
|
404
|
+
-- source is cleanly re-registered and maintained on the next write. (The old
|
|
405
|
+
-- `count(*)` on the source proved nothing; maintenance is observed via the MV.)
|
|
406
|
+
-- ===================================
|
|
407
|
+
|
|
408
|
+
create table dr (id integer primary key, x integer);
|
|
409
|
+
insert into dr values (1, 1);
|
|
410
|
+
create materialized view dr_ix as select id, x from dr;
|
|
411
|
+
select id, x from dr_ix order by id;
|
|
412
|
+
→ [{"id":1,"x":1}]
|
|
413
|
+
|
|
414
|
+
-- after DROP the name no longer resolves (plan + backing detached)
|
|
415
|
+
drop materialized view dr_ix;
|
|
416
|
+
select * from dr_ix;
|
|
417
|
+
-- error: not found
|
|
418
|
+
|
|
419
|
+
-- a fresh row-time MV on the same source proves re-registration is clean: the next
|
|
420
|
+
-- source write is maintained against the NEW plan (and not the detached old one).
|
|
421
|
+
create materialized view dr_ix2 as select x, id from dr order by x;
|
|
422
|
+
insert into dr values (2, 2);
|
|
423
|
+
select x, id from dr_ix2 order by x;
|
|
424
|
+
→ [{"x":1,"id":1},{"x":2,"id":2}]
|
|
425
|
+
|
|
426
|
+
-- ===================================
|
|
427
|
+
-- 10. UPSERT do-update write-through (DML hook site 1). `on conflict do update`
|
|
428
|
+
-- drives an `update` maintenance op with old=existing, new=the UPDATED image
|
|
429
|
+
-- (existing row cloned, then SET assignments applied) — NOT the proposed insert
|
|
430
|
+
-- row. A `do update … where` that evaluates false is DO NOTHING (backing
|
|
431
|
+
-- unchanged).
|
|
432
|
+
-- ===================================
|
|
433
|
+
|
|
434
|
+
create table up (id integer primary key, x integer);
|
|
435
|
+
insert into up values (1, 10);
|
|
436
|
+
create materialized view up_ix as select x, id from up order by x;
|
|
437
|
+
select x, id from up_ix order by x;
|
|
438
|
+
→ [{"x":10,"id":1}]
|
|
439
|
+
|
|
440
|
+
-- conflict on id=1 → DO UPDATE sets x to excluded.x (999); MV reflects updated image
|
|
441
|
+
insert into up values (1, 999) on conflict do update set x = excluded.x;
|
|
442
|
+
select x, id from up_ix order by x;
|
|
443
|
+
→ [{"x":999,"id":1}]
|
|
444
|
+
|
|
445
|
+
-- DO UPDATE … WHERE false ⇒ DO NOTHING ⇒ backing unchanged
|
|
446
|
+
insert into up values (1, 5) on conflict do update set x = excluded.x where excluded.x < 0;
|
|
447
|
+
select x, id from up_ix order by x;
|
|
448
|
+
→ [{"x":999,"id":1}]
|
|
449
|
+
|
|
450
|
+
-- DO UPDATE … WHERE true ⇒ update applies (and moves the backing key, x is keyed)
|
|
451
|
+
insert into up values (1, 42) on conflict do update set x = excluded.x where excluded.x > 0;
|
|
452
|
+
select x, id from up_ix order by x;
|
|
453
|
+
→ [{"x":42,"id":1}]
|
|
454
|
+
|
|
455
|
+
-- ===================================
|
|
456
|
+
-- 11. INSERT OR REPLACE eviction (DML hook site 2). A replace on an existing PK
|
|
457
|
+
-- keeps the source PK fixed, so the backing key (which carries the source PK)
|
|
458
|
+
-- does NOT move — the backing PAYLOAD updates in place for that key. Maintenance
|
|
459
|
+
-- fires as `update` with old=replacedRow, new=the inserted row.
|
|
460
|
+
-- ===================================
|
|
461
|
+
|
|
462
|
+
create table ir (id integer primary key, x integer);
|
|
463
|
+
insert into ir values (1, 10);
|
|
464
|
+
create materialized view ir_ix as select id, x from ir;
|
|
465
|
+
select id, x from ir_ix order by id;
|
|
466
|
+
→ [{"id":1,"x":10}]
|
|
467
|
+
|
|
468
|
+
insert or replace into ir values (1, 7);
|
|
469
|
+
select id, x from ir_ix order by id;
|
|
470
|
+
→ [{"id":1,"x":7}]
|
|
471
|
+
|
|
472
|
+
-- ===================================
|
|
473
|
+
-- 12. UPDATE-causes-REPLACE eviction (DML hook site 4) via a `primary key on
|
|
474
|
+
-- conflict replace` source. A key-changing UPDATE that moves one row's PK onto
|
|
475
|
+
-- another live row's PK evicts the occupant (site 4 → `delete` op) and moves the
|
|
476
|
+
-- updated row (site 5 → `update` op). Both deltas must net correctly in the
|
|
477
|
+
-- backing: the evicted image gone, the moved image present under the new key.
|
|
478
|
+
-- ===================================
|
|
479
|
+
|
|
480
|
+
create table pr (id integer primary key on conflict replace, v text);
|
|
481
|
+
insert into pr values (1, 'a'), (2, 'b');
|
|
482
|
+
create materialized view pr_ix as select v, id from pr;
|
|
483
|
+
select v, id from pr_ix order by id;
|
|
484
|
+
→ [{"v":"a","id":1},{"v":"b","id":2}]
|
|
485
|
+
|
|
486
|
+
-- move id=1 onto id=2: (2,'b') is evicted, (1,'a') becomes (2,'a')
|
|
487
|
+
update pr set id = 2 where id = 1;
|
|
488
|
+
select v, id from pr_ix order by id;
|
|
489
|
+
→ [{"v":"a","id":2}]
|
|
490
|
+
|
|
491
|
+
-- ===================================
|
|
492
|
+
-- 13. Savepoint interaction — lazy backing-connection registration. The backing
|
|
493
|
+
-- connection attaches lazily on first maintenance; the active savepoint stack
|
|
494
|
+
-- must be replayed onto it so ROLLBACK TO lands on a real entry. Mirrors
|
|
495
|
+
-- 04a-savepoint-lazy-attach.sqllogic for the backing connection.
|
|
496
|
+
-- ===================================
|
|
497
|
+
|
|
498
|
+
create table sv (id integer primary key, x integer);
|
|
499
|
+
create materialized view sv_ix as select id, x from sv;
|
|
500
|
+
|
|
501
|
+
begin;
|
|
502
|
+
insert into sv values (1, 10);
|
|
503
|
+
savepoint sp1;
|
|
504
|
+
insert into sv values (2, 20);
|
|
505
|
+
insert into sv values (3, 30);
|
|
506
|
+
-- all three visible mid-transaction
|
|
507
|
+
select id, x from sv_ix order by id;
|
|
508
|
+
→ [{"id":1,"x":10},{"id":2,"x":20},{"id":3,"x":30}]
|
|
509
|
+
|
|
510
|
+
-- ROLLBACK TO discards the post-savepoint backing deltas; row 1 (pre-savepoint) stays
|
|
511
|
+
rollback to sp1;
|
|
512
|
+
select id, x from sv_ix order by id;
|
|
513
|
+
→ [{"id":1,"x":10}]
|
|
514
|
+
|
|
515
|
+
commit;
|
|
516
|
+
-- the committed pre-savepoint delta persists
|
|
517
|
+
select id, x from sv_ix order by id;
|
|
518
|
+
→ [{"id":1,"x":10}]
|
|
519
|
+
|
|
520
|
+
-- ===================================
|
|
521
|
+
-- 14. NULL / 3-valued partial predicate. `inScope` requires the predicate to be
|
|
522
|
+
-- unambiguously TRUE, so a NULL predicate value is OUT of scope. A nullable
|
|
523
|
+
-- projected PAYLOAD column (distinct from the predicate) carries NULL through to
|
|
524
|
+
-- the backing row.
|
|
525
|
+
-- ===================================
|
|
526
|
+
|
|
527
|
+
create table np (id integer primary key, x integer null, payload text null);
|
|
528
|
+
create materialized view np_ix as select payload, x, id from np where x > 0;
|
|
529
|
+
|
|
530
|
+
-- x = NULL ⇒ predicate is NULL (not TRUE) ⇒ no backing row
|
|
531
|
+
insert into np values (1, null, 'p1');
|
|
532
|
+
select payload, x, id from np_ix order by id;
|
|
533
|
+
→ []
|
|
534
|
+
|
|
535
|
+
-- x = 5 ⇒ in scope ⇒ backing row appears
|
|
536
|
+
insert into np values (2, 5, 'p2');
|
|
537
|
+
select payload, x, id from np_ix order by id;
|
|
538
|
+
→ [{"payload":"p2","x":5,"id":2}]
|
|
539
|
+
|
|
540
|
+
-- in-scope → NULL ⇒ row leaves
|
|
541
|
+
update np set x = null where id = 2;
|
|
542
|
+
select payload, x, id from np_ix order by id;
|
|
543
|
+
→ []
|
|
544
|
+
|
|
545
|
+
-- NULL → positive ⇒ row enters
|
|
546
|
+
update np set x = 9 where id = 1;
|
|
547
|
+
select payload, x, id from np_ix order by id;
|
|
548
|
+
→ [{"payload":"p1","x":9,"id":1}]
|
|
549
|
+
|
|
550
|
+
-- nullable PAYLOAD carries NULL into the backing row (payload NULL ≠ predicate NULL)
|
|
551
|
+
insert into np values (3, 4, null);
|
|
552
|
+
select payload, x, id from np_ix order by id;
|
|
553
|
+
→ [{"payload":"p1","x":9,"id":1},{"payload":null,"x":4,"id":3}]
|
|
554
|
+
|
|
555
|
+
-- ===================================
|
|
556
|
+
-- 15. Same-key & shared-leading-key updates — delete-old/upsert-new ordering. The
|
|
557
|
+
-- backing key is the ORDER BY column(s) seeded with the source PK, so it always
|
|
558
|
+
-- includes the PK; two distinct source rows can never share a backing key. A
|
|
559
|
+
-- payload-only (or no-op) update keeps the backing key fixed, exercising the
|
|
560
|
+
-- delete-then-upsert-at-the-same-key path (must net to the row present, updated).
|
|
561
|
+
-- ===================================
|
|
562
|
+
|
|
563
|
+
create table sk (id integer primary key, x integer, payload text);
|
|
564
|
+
insert into sk values (1, 5, 'a'), (2, 8, 'b');
|
|
565
|
+
create materialized view sk_ix as select x, id, payload from sk order by x;
|
|
566
|
+
select x, id, payload from sk_ix order by x, id;
|
|
567
|
+
→ [{"x":5,"id":1,"payload":"a"},{"x":8,"id":2,"payload":"b"}]
|
|
568
|
+
|
|
569
|
+
-- payload-only update: backing key [x,id]=[5,1] unchanged ⇒ delete-then-upsert same key
|
|
570
|
+
update sk set payload = 'a2' where id = 1;
|
|
571
|
+
select x, id, payload from sk_ix order by x, id;
|
|
572
|
+
→ [{"x":5,"id":1,"payload":"a2"},{"x":8,"id":2,"payload":"b"}]
|
|
573
|
+
|
|
574
|
+
-- true no-op update (set x = x): same key, same image ⇒ net unchanged
|
|
575
|
+
update sk set x = x where id = 1;
|
|
576
|
+
select x, id, payload from sk_ix order by x, id;
|
|
577
|
+
→ [{"x":5,"id":1,"payload":"a2"},{"x":8,"id":2,"payload":"b"}]
|
|
578
|
+
|
|
579
|
+
-- shared leading key, distinct PK: move id=1 to x=8 (now both have x=8) — distinct
|
|
580
|
+
-- backing keys [8,1] and [8,2], both present (the PK tiebreak keeps them apart)
|
|
581
|
+
update sk set x = 8 where id = 1;
|
|
582
|
+
select x, id, payload from sk_ix order by x, id;
|
|
583
|
+
→ [{"x":8,"id":1,"payload":"a2"},{"x":8,"id":2,"payload":"b"}]
|
|
584
|
+
|
|
585
|
+
-- ===================================
|
|
586
|
+
-- 16. Source schema change + refresh / recreate re-registration. A SHAPE-SHIFTING
|
|
587
|
+
-- structural ALTER (here: DROP NOT NULL on a PROJECTED column, which shifts the body's
|
|
588
|
+
-- output nullability) fires a `table_modified` event that marks the MV stale and
|
|
589
|
+
-- DETACHES the row-time plan: the MV keeps reading its last snapshot, and a post-alter
|
|
590
|
+
-- source write is NOT maintained (stale until refresh or recreate). BOTH `refresh
|
|
591
|
+
-- materialized view` and drop+recreate restore the MV — they (a) rebuild the snapshot
|
|
592
|
+
-- to include the missed row(s) AND (b) re-register a fresh row-time plan so subsequent
|
|
593
|
+
-- source writes resume propagating. Dropping the source instead makes the MV read a
|
|
594
|
+
-- clean stale error. (A structural ALTER the body provably does NOT read now keeps the
|
|
595
|
+
-- MV LIVE instead — see 53.4-materialized-view-structural-alter-restore.sqllogic.)
|
|
596
|
+
-- ===================================
|
|
597
|
+
|
|
598
|
+
-- 16a. `refresh materialized view` re-registers the row-time plan (the bug this file's
|
|
599
|
+
-- ticket fixed: refresh used to rebuild the snapshot but leave the plan detached,
|
|
600
|
+
-- so post-refresh source writes were silently dropped).
|
|
601
|
+
create table al (id integer primary key, x integer);
|
|
602
|
+
insert into al values (1, 10);
|
|
603
|
+
create materialized view al_ix as select id, x from al;
|
|
604
|
+
|
|
605
|
+
-- a shape-shifting ALTER (drop not null on the projected column x) marks the MV stale and detaches the row-time plan
|
|
606
|
+
alter table al alter column x drop not null;
|
|
607
|
+
-- the post-alter source write is NOT maintained (plan detached); the MV serves the
|
|
608
|
+
-- pre-alter snapshot rather than crashing
|
|
609
|
+
insert into al (id, x) values (2, 20);
|
|
610
|
+
select id, x from al_ix order by id;
|
|
611
|
+
→ [{"id":1,"x":10}]
|
|
612
|
+
|
|
613
|
+
-- refresh rebuilds the snapshot to include the missed row AND re-registers the plan
|
|
614
|
+
refresh materialized view al_ix;
|
|
615
|
+
select id, x from al_ix order by id;
|
|
616
|
+
→ [{"id":1,"x":10},{"id":2,"x":20}]
|
|
617
|
+
|
|
618
|
+
-- maintenance has resumed against the re-registered plan: a subsequent write IS reflected
|
|
619
|
+
insert into al (id, x) values (3, 30);
|
|
620
|
+
select id, x from al_ix order by id;
|
|
621
|
+
→ [{"id":1,"x":10},{"id":2,"x":20},{"id":3,"x":30}]
|
|
622
|
+
|
|
623
|
+
-- 16b. drop+recreate is the other restore path; it must also end fully maintained.
|
|
624
|
+
create table al2 (id integer primary key, x integer);
|
|
625
|
+
insert into al2 values (1, 10);
|
|
626
|
+
create materialized view al2_ix as select id, x from al2;
|
|
627
|
+
|
|
628
|
+
alter table al2 alter column x drop not null;
|
|
629
|
+
insert into al2 (id, x) values (2, 20);
|
|
630
|
+
select id, x from al2_ix order by id;
|
|
631
|
+
→ [{"id":1,"x":10}]
|
|
632
|
+
|
|
633
|
+
-- recreate re-registers a fresh row-time plan; the rebuild reflects all source rows
|
|
634
|
+
drop materialized view al2_ix;
|
|
635
|
+
create materialized view al2_ix as select id, x from al2;
|
|
636
|
+
select id, x from al2_ix order by id;
|
|
637
|
+
→ [{"id":1,"x":10},{"id":2,"x":20}]
|
|
638
|
+
|
|
639
|
+
-- maintenance has resumed against the new plan
|
|
640
|
+
insert into al2 (id, x) values (3, 30);
|
|
641
|
+
select id, x from al2_ix order by id;
|
|
642
|
+
→ [{"id":1,"x":10},{"id":2,"x":20},{"id":3,"x":30}]
|
|
643
|
+
|
|
644
|
+
-- 16c. Idempotent-registration guard — refreshing a NOT-stale MV re-registers the plan
|
|
645
|
+
-- (release-then-rebuild), which must leave maintenance working with no duplicate
|
|
646
|
+
-- rowTimeBySource entry breaking subsequent writes.
|
|
647
|
+
create table nr (id integer primary key, x integer);
|
|
648
|
+
insert into nr values (1, 10);
|
|
649
|
+
create materialized view nr_ix as select id, x from nr;
|
|
650
|
+
-- refresh a never-stale MV: harmless no-op re-attach
|
|
651
|
+
refresh materialized view nr_ix;
|
|
652
|
+
-- maintenance still fires after the redundant re-register
|
|
653
|
+
insert into nr values (2, 20);
|
|
654
|
+
select id, x from nr_ix order by id;
|
|
655
|
+
→ [{"id":1,"x":10},{"id":2,"x":20}]
|
|
656
|
+
|
|
657
|
+
-- 16d. Compound-PK MV resumes maintenance after a refresh.
|
|
658
|
+
create table cpr (a integer, b integer, v integer, primary key (a, b));
|
|
659
|
+
insert into cpr values (1, 1, 100);
|
|
660
|
+
create materialized view cpr_ix as select a, b, v from cpr;
|
|
661
|
+
alter table cpr alter column v drop not null;
|
|
662
|
+
insert into cpr (a, b, v) values (1, 2, 200);
|
|
663
|
+
select a, b, v from cpr_ix order by a, b;
|
|
664
|
+
→ [{"a":1,"b":1,"v":100}]
|
|
665
|
+
refresh materialized view cpr_ix;
|
|
666
|
+
select a, b, v from cpr_ix order by a, b;
|
|
667
|
+
→ [{"a":1,"b":1,"v":100},{"a":1,"b":2,"v":200}]
|
|
668
|
+
-- subsequent compound-PK write is maintained
|
|
669
|
+
insert into cpr (a, b, v) values (2, 1, 300);
|
|
670
|
+
select a, b, v from cpr_ix order by a, b;
|
|
671
|
+
→ [{"a":1,"b":1,"v":100},{"a":1,"b":2,"v":200},{"a":2,"b":1,"v":300}]
|
|
672
|
+
|
|
673
|
+
-- 16e. Partial (WHERE) MV resumes maintenance after a refresh, predicate still honored.
|
|
674
|
+
create table par (id integer primary key, x integer);
|
|
675
|
+
insert into par values (1, 5);
|
|
676
|
+
create materialized view par_ix as select id, x from par where x > 0;
|
|
677
|
+
alter table par alter column x drop not null;
|
|
678
|
+
-- one in-scope and one out-of-scope write while stale (both unmaintained)
|
|
679
|
+
insert into par (id, x) values (2, 8);
|
|
680
|
+
insert into par (id, x) values (3, -1);
|
|
681
|
+
select id, x from par_ix order by id;
|
|
682
|
+
→ [{"id":1,"x":5}]
|
|
683
|
+
refresh materialized view par_ix;
|
|
684
|
+
-- rebuild applies the predicate: the in-scope missed row appears, the out-of-scope does not
|
|
685
|
+
select id, x from par_ix order by id;
|
|
686
|
+
→ [{"id":1,"x":5},{"id":2,"x":8}]
|
|
687
|
+
-- maintenance resumed: in-scope write reflected, out-of-scope still excluded
|
|
688
|
+
insert into par (id, x) values (4, 10);
|
|
689
|
+
insert into par (id, x) values (5, -2);
|
|
690
|
+
select id, x from par_ix order by id;
|
|
691
|
+
→ [{"id":1,"x":5},{"id":2,"x":8},{"id":4,"x":10}]
|
|
692
|
+
|
|
693
|
+
-- dropping the SOURCE of a row-time MV makes a read a clean stale diagnostic, not a crash
|
|
694
|
+
create table ds (id integer primary key, x integer);
|
|
695
|
+
insert into ds values (1, 10);
|
|
696
|
+
create materialized view ds_ix as select id, x from ds;
|
|
697
|
+
drop table ds;
|
|
698
|
+
select id, x from ds_ix order by id;
|
|
699
|
+
-- error: is stale
|
|
700
|
+
|
|
701
|
+
-- ===================================
|
|
702
|
+
-- 17. Compound-PK extensions beyond §5 — a compound delete and a compound
|
|
703
|
+
-- key-changing update (moving one PK column). Backing key = compound source PK.
|
|
704
|
+
-- ===================================
|
|
705
|
+
|
|
706
|
+
create table cpx (a integer, b integer, v integer, primary key (a, b));
|
|
707
|
+
insert into cpx values (1, 1, 100), (1, 2, 200), (2, 1, 300);
|
|
708
|
+
create materialized view cpx_ix as select a, b, v from cpx;
|
|
709
|
+
select a, b, v from cpx_ix order by a, b;
|
|
710
|
+
→ [{"a":1,"b":1,"v":100},{"a":1,"b":2,"v":200},{"a":2,"b":1,"v":300}]
|
|
711
|
+
|
|
712
|
+
-- compound delete removes exactly the addressed backing row
|
|
713
|
+
delete from cpx where a = 1 and b = 1;
|
|
714
|
+
select a, b, v from cpx_ix order by a, b;
|
|
715
|
+
→ [{"a":1,"b":2,"v":200},{"a":2,"b":1,"v":300}]
|
|
716
|
+
|
|
717
|
+
-- compound key-change: move (1,2)→(1,9) — old compound key deleted, new upserted
|
|
718
|
+
update cpx set b = 9 where a = 1 and b = 2;
|
|
719
|
+
select a, b, v from cpx_ix order by a, b;
|
|
720
|
+
→ [{"a":1,"b":9,"v":200},{"a":2,"b":1,"v":300}]
|
|
721
|
+
|
|
722
|
+
-- ===================================
|
|
723
|
+
-- 18. Boundary cases — no maintenance op should fire (or it must net to no change).
|
|
724
|
+
-- ===================================
|
|
725
|
+
|
|
726
|
+
create table bd (id integer primary key, x integer);
|
|
727
|
+
insert into bd values (1, 5);
|
|
728
|
+
create materialized view bd_ix as select x, id from bd where x > 0;
|
|
729
|
+
select x, id from bd_ix order by id;
|
|
730
|
+
→ [{"x":5,"id":1}]
|
|
731
|
+
|
|
732
|
+
-- delete of a non-existent row: vtab returns no row ⇒ maintenance never fires
|
|
733
|
+
delete from bd where id = 999;
|
|
734
|
+
select x, id from bd_ix order by id;
|
|
735
|
+
→ [{"x":5,"id":1}]
|
|
736
|
+
|
|
737
|
+
-- delete of an out-of-scope row from a partial MV: inScope(oldRow) is false ⇒ no op
|
|
738
|
+
insert into bd values (2, -3);
|
|
739
|
+
delete from bd where id = 2;
|
|
740
|
+
select x, id from bd_ix order by id;
|
|
741
|
+
→ [{"x":5,"id":1}]
|
|
742
|
+
|
|
743
|
+
-- ===================================
|
|
744
|
+
-- 19. Deterministic expression-projection columns
|
|
745
|
+
-- (materialized-view-rowtime-expression-projections). A backing output column may
|
|
746
|
+
-- be a deterministic scalar expression over the single source row (arithmetic,
|
|
747
|
+
-- deterministic functions, CASE, CAST, ‖) — still a pure per-row projection
|
|
748
|
+
-- (O(log n), no body re-execution): each source write evaluates project(row) for
|
|
749
|
+
-- the computed columns, so they stay consistent under insert / update / delete
|
|
750
|
+
-- exactly as a plain view would. PK columns stay passthrough; the computed columns
|
|
751
|
+
-- are not part of the backing key.
|
|
752
|
+
-- ===================================
|
|
753
|
+
|
|
754
|
+
create table ev (id integer primary key, v integer, name text);
|
|
755
|
+
insert into ev values (1, 10, 'Alice'), (2, 20, 'Bob');
|
|
756
|
+
|
|
757
|
+
-- arithmetic + deterministic-function projection columns over a single source
|
|
758
|
+
create materialized view ev_ix as select id, v + 1 as v1, abs(v) as av, lower(name) as ln from ev;
|
|
759
|
+
|
|
760
|
+
-- computed columns equal the body at create time (MV ≡ faster view)
|
|
761
|
+
select id, v1, av, ln from ev_ix order by id;
|
|
762
|
+
→ [{"id":1,"v1":11,"av":10,"ln":"alice"},{"id":2,"v1":21,"av":20,"ln":"bob"}]
|
|
763
|
+
|
|
764
|
+
-- INSERT: the new row's computed columns appear (negative v exercises abs)
|
|
765
|
+
insert into ev values (3, -5, 'CARA');
|
|
766
|
+
select id, v1, av, ln from ev_ix order by id;
|
|
767
|
+
→ [{"id":1,"v1":11,"av":10,"ln":"alice"},{"id":2,"v1":21,"av":20,"ln":"bob"},{"id":3,"v1":-4,"av":5,"ln":"cara"}]
|
|
768
|
+
|
|
769
|
+
-- UPDATE of a source column the expressions read: EVERY computed column tracks it
|
|
770
|
+
-- (reads-own-writes), not just passthrough columns
|
|
771
|
+
update ev set v = 100, name = 'Zed' where id = 1;
|
|
772
|
+
select id, v1, av, ln from ev_ix order by id;
|
|
773
|
+
→ [{"id":1,"v1":101,"av":100,"ln":"zed"},{"id":2,"v1":21,"av":20,"ln":"bob"},{"id":3,"v1":-4,"av":5,"ln":"cara"}]
|
|
774
|
+
|
|
775
|
+
-- DELETE removes the backing row (and its computed columns) entirely
|
|
776
|
+
delete from ev where id = 2;
|
|
777
|
+
select id, v1, av, ln from ev_ix order by id;
|
|
778
|
+
→ [{"id":1,"v1":101,"av":100,"ln":"zed"},{"id":3,"v1":-4,"av":5,"ln":"cara"}]
|
|
779
|
+
|
|
780
|
+
-- CASE / CAST / ‖ concat projection forms are accepted (the runtime evaluates them,
|
|
781
|
+
-- so a computed backing value is byte-for-byte what `select <body>` produces)
|
|
782
|
+
create materialized view ev_ix2 as
|
|
783
|
+
select id, case when v > 0 then 'pos' else 'neg' end as sign, cast(v as text) as vt, name || '!' as nx from ev;
|
|
784
|
+
select id, sign, vt, nx from ev_ix2 order by id;
|
|
785
|
+
→ [{"id":1,"sign":"pos","vt":"100","nx":"Zed!"},{"id":3,"sign":"neg","vt":"-5","nx":"CARA!"}]
|
|
786
|
+
|
|
787
|
+
-- ===================================
|
|
788
|
+
-- 20. Expression columns under a partial WHERE — key-changing update and
|
|
789
|
+
-- predicate-scope transitions. The backing key is the passthrough (order-by + PK)
|
|
790
|
+
-- columns; the computed columns ride each delete-old/upsert-new exactly like the
|
|
791
|
+
-- passthrough payload does.
|
|
792
|
+
-- ===================================
|
|
793
|
+
|
|
794
|
+
create table evp (id integer primary key, v integer, name text);
|
|
795
|
+
insert into evp values (1, 5, 'aa'), (2, -3, 'bb'), (3, 8, 'cc');
|
|
796
|
+
create materialized view evp_ix as select id, v, v * 10 as v10, upper(name) as un from evp where v > 0 order by v;
|
|
797
|
+
select id, v, v10, un from evp_ix order by v;
|
|
798
|
+
→ [{"id":1,"v":5,"v10":50,"un":"AA"},{"id":3,"v":8,"v10":80,"un":"CC"}]
|
|
799
|
+
|
|
800
|
+
-- in-scope key-changing update (v is the leading backing key AND read by v10): old key
|
|
801
|
+
-- deleted, new upserted, and v10 tracks the new value
|
|
802
|
+
update evp set v = 6 where id = 1;
|
|
803
|
+
select id, v, v10, un from evp_ix order by v;
|
|
804
|
+
→ [{"id":1,"v":6,"v10":60,"un":"AA"},{"id":3,"v":8,"v10":80,"un":"CC"}]
|
|
805
|
+
|
|
806
|
+
-- predicate-scope transition IN: id=2 enters scope (v -3 → 4); its computed columns appear
|
|
807
|
+
update evp set v = 4 where id = 2;
|
|
808
|
+
select id, v, v10, un from evp_ix order by v;
|
|
809
|
+
→ [{"id":2,"v":4,"v10":40,"un":"BB"},{"id":1,"v":6,"v10":60,"un":"AA"},{"id":3,"v":8,"v10":80,"un":"CC"}]
|
|
810
|
+
|
|
811
|
+
-- predicate-scope transition OUT: id=3 leaves scope (v 8 → -1); its backing row (and
|
|
812
|
+
-- computed columns) disappear
|
|
813
|
+
update evp set v = -1 where id = 3;
|
|
814
|
+
select id, v, v10, un from evp_ix order by v;
|
|
815
|
+
→ [{"id":2,"v":4,"v10":40,"un":"BB"},{"id":1,"v":6,"v10":60,"un":"AA"}]
|
|
816
|
+
|
|
817
|
+
-- ===================================
|
|
818
|
+
-- 21. Per-statement maintenance batching (materialized-view-rowtime-per-statement-
|
|
819
|
+
-- batching). A bulk insert/update/delete resolves each covering MV's backing
|
|
820
|
+
-- connection ONCE per statement (a per-generator-run cache) and applies every
|
|
821
|
+
-- row's backing delta immediately to that one connection's pending layer. This
|
|
822
|
+
-- asserts the amortized path is behaviorally identical to per-row maintenance:
|
|
823
|
+
-- all rows of a bulk statement land in the backing, a bulk update moves every
|
|
824
|
+
-- key, a bulk delete removes every matching backing row, and a statement/txn
|
|
825
|
+
-- rollback reverts the whole bulk backing delta in lockstep.
|
|
826
|
+
-- ===================================
|
|
827
|
+
|
|
828
|
+
create table blk (id integer primary key, x integer);
|
|
829
|
+
create materialized view blk_ix as select x, id from blk order by x, id;
|
|
830
|
+
|
|
831
|
+
-- bulk INSERT of many rows in ONE statement: every row's backing maintenance
|
|
832
|
+
-- accumulates on the per-statement cached backing connection.
|
|
833
|
+
insert into blk values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50);
|
|
834
|
+
select x, id from blk_ix order by x;
|
|
835
|
+
→ [{"x":10,"id":1},{"x":20,"id":2},{"x":30,"id":3},{"x":40,"id":4},{"x":50,"id":5}]
|
|
836
|
+
|
|
837
|
+
-- bulk UPDATE in ONE statement moves every projected backing key (delete-old/upsert-new
|
|
838
|
+
-- per row); the PK tiebreak keeps the backing keys distinct so none collide.
|
|
839
|
+
update blk set x = x + 100;
|
|
840
|
+
select x, id from blk_ix order by x;
|
|
841
|
+
→ [{"x":110,"id":1},{"x":120,"id":2},{"x":130,"id":3},{"x":140,"id":4},{"x":150,"id":5}]
|
|
842
|
+
|
|
843
|
+
-- bulk DELETE in ONE statement removes exactly the matching backing rows
|
|
844
|
+
delete from blk where id <= 3;
|
|
845
|
+
select x, id from blk_ix order by x;
|
|
846
|
+
→ [{"x":140,"id":4},{"x":150,"id":5}]
|
|
847
|
+
|
|
848
|
+
-- a transaction rollback reverts a bulk statement's whole backing delta in lockstep
|
|
849
|
+
begin;
|
|
850
|
+
insert into blk values (6, 60), (7, 70), (8, 80);
|
|
851
|
+
select x, id from blk_ix order by x;
|
|
852
|
+
→ [{"x":60,"id":6},{"x":70,"id":7},{"x":80,"id":8},{"x":140,"id":4},{"x":150,"id":5}]
|
|
853
|
+
rollback;
|
|
854
|
+
select x, id from blk_ix order by x;
|
|
855
|
+
→ [{"x":140,"id":4},{"x":150,"id":5}]
|
|
856
|
+
|
|
857
|
+
-- reads-own-writes BETWEEN statements within one explicit transaction: each bulk
|
|
858
|
+
-- statement's maintenance is visible to the next statement's read (the property
|
|
859
|
+
-- per-statement batching must preserve), and the whole transaction commits in lockstep.
|
|
860
|
+
begin;
|
|
861
|
+
insert into blk values (6, 60), (7, 70);
|
|
862
|
+
select x, id from blk_ix order by x;
|
|
863
|
+
→ [{"x":60,"id":6},{"x":70,"id":7},{"x":140,"id":4},{"x":150,"id":5}]
|
|
864
|
+
update blk set x = 200 where id = 4;
|
|
865
|
+
select x, id from blk_ix order by x;
|
|
866
|
+
→ [{"x":60,"id":6},{"x":70,"id":7},{"x":150,"id":5},{"x":200,"id":4}]
|
|
867
|
+
commit;
|
|
868
|
+
select x, id from blk_ix order by x;
|
|
869
|
+
→ [{"x":60,"id":6},{"x":70,"id":7},{"x":150,"id":5},{"x":200,"id":4}]
|
|
870
|
+
|
|
871
|
+
-- bulk write through a 2-level chain in ONE statement: the per-statement cache is
|
|
872
|
+
-- keyed by backing base, so it holds BOTH the inner and outer backing connections and
|
|
873
|
+
-- every cascade level amortizes. Insert/update/delete reflect at the deepest level.
|
|
874
|
+
create table bc (id integer primary key, x integer);
|
|
875
|
+
create materialized view bc1 as select id, x from bc;
|
|
876
|
+
create materialized view bc2 as select id, x from bc1;
|
|
877
|
+
insert into bc values (1, 1), (2, 2), (3, 3), (4, 4);
|
|
878
|
+
select id, x from bc2 order by id;
|
|
879
|
+
→ [{"id":1,"x":1},{"id":2,"x":2},{"id":3,"x":3},{"id":4,"x":4}]
|
|
880
|
+
update bc set x = x * 10;
|
|
881
|
+
select id, x from bc2 order by id;
|
|
882
|
+
→ [{"id":1,"x":10},{"id":2,"x":20},{"id":3,"x":30},{"id":4,"x":40}]
|
|
883
|
+
delete from bc where id in (2, 4);
|
|
884
|
+
select id, x from bc2 order by id;
|
|
885
|
+
→ [{"id":1,"x":10},{"id":3,"x":30}]
|
|
886
|
+
|
|
887
|
+
-- ===================================
|
|
888
|
+
-- 22. Single-source aggregate bodies (`group by` over bare columns) — the
|
|
889
|
+
-- residual-recompute arm. Per source write, the affected group's backing row is
|
|
890
|
+
-- recomputed from live state (delete old slice → run the group-keyed residual →
|
|
891
|
+
-- upsert); an emptied group's backing row is removed. Observable purely by reading
|
|
892
|
+
-- the MV. (materialized-view-rowtime-residual-recompute)
|
|
893
|
+
-- ===================================
|
|
894
|
+
|
|
895
|
+
create table ag (id integer primary key, k integer, v integer);
|
|
896
|
+
insert into ag values (1, 1, 10), (2, 1, 5), (3, 2, 7);
|
|
897
|
+
create materialized view ag_sum as select k, count(*) as c, sum(v) as s from ag group by k;
|
|
898
|
+
|
|
899
|
+
-- MV equals the body at create time (groups k=1 {10,5}, k=2 {7}).
|
|
900
|
+
select k, c, s from ag_sum order by k;
|
|
901
|
+
→ [{"k":1,"c":2,"s":15},{"k":2,"c":1,"s":7}]
|
|
902
|
+
|
|
903
|
+
-- INSERT into an existing group recomputes only that group.
|
|
904
|
+
insert into ag values (4, 2, 3);
|
|
905
|
+
select k, c, s from ag_sum order by k;
|
|
906
|
+
→ [{"k":1,"c":2,"s":15},{"k":2,"c":2,"s":10}]
|
|
907
|
+
|
|
908
|
+
-- INSERT that opens a NEW group adds a backing row.
|
|
909
|
+
insert into ag values (5, 3, 100);
|
|
910
|
+
select k, c, s from ag_sum order by k;
|
|
911
|
+
→ [{"k":1,"c":2,"s":15},{"k":2,"c":2,"s":10},{"k":3,"c":1,"s":100}]
|
|
912
|
+
|
|
913
|
+
-- Non-key UPDATE recomputes the affected group's aggregate.
|
|
914
|
+
update ag set v = 20 where id = 1;
|
|
915
|
+
select k, c, s from ag_sum order by k;
|
|
916
|
+
→ [{"k":1,"c":2,"s":25},{"k":2,"c":2,"s":10},{"k":3,"c":1,"s":100}]
|
|
917
|
+
|
|
918
|
+
-- Group-key-changing UPDATE: move id=2 from k=1 to k=2 (BOTH old + new groups recomputed).
|
|
919
|
+
update ag set k = 2 where id = 2;
|
|
920
|
+
select k, c, s from ag_sum order by k;
|
|
921
|
+
→ [{"k":1,"c":1,"s":20},{"k":2,"c":3,"s":15},{"k":3,"c":1,"s":100}]
|
|
922
|
+
|
|
923
|
+
-- DELETE the last row of a group → its backing row disappears (emptied group).
|
|
924
|
+
delete from ag where id = 5;
|
|
925
|
+
select k, c, s from ag_sum order by k;
|
|
926
|
+
→ [{"k":1,"c":1,"s":20},{"k":2,"c":3,"s":15}]
|
|
927
|
+
|
|
928
|
+
-- Mid-transaction visibility + rollback reverts in lockstep (reads-own-writes).
|
|
929
|
+
begin;
|
|
930
|
+
insert into ag values (6, 1, 1000);
|
|
931
|
+
select k, c, s from ag_sum order by k;
|
|
932
|
+
→ [{"k":1,"c":2,"s":1020},{"k":2,"c":3,"s":15}]
|
|
933
|
+
rollback;
|
|
934
|
+
select k, c, s from ag_sum order by k;
|
|
935
|
+
→ [{"k":1,"c":1,"s":20},{"k":2,"c":3,"s":15}]
|
|
936
|
+
|
|
937
|
+
-- 22b. Partial-WHERE aggregate — only rows whose predicate is TRUE contribute; a
|
|
938
|
+
-- predicate-scope transition recomputes the affected group, and a group whose
|
|
939
|
+
-- every contributing row leaves scope loses its backing row.
|
|
940
|
+
create table agp (id integer primary key, k integer, v integer);
|
|
941
|
+
insert into agp values (1, 1, 5), (2, 1, -2), (3, 2, 8);
|
|
942
|
+
create materialized view agp_sum as select k, sum(v) as s from agp where v > 0 group by k;
|
|
943
|
+
select k, s from agp_sum order by k;
|
|
944
|
+
→ [{"k":1,"s":5},{"k":2,"s":8}]
|
|
945
|
+
|
|
946
|
+
-- id=2 enters scope (v: -2 → 3): group k=1 recomputed.
|
|
947
|
+
update agp set v = 3 where id = 2;
|
|
948
|
+
select k, s from agp_sum order by k;
|
|
949
|
+
→ [{"k":1,"s":8},{"k":2,"s":8}]
|
|
950
|
+
|
|
951
|
+
-- id=1 leaves scope (v: 5 → -1): group k=1 recomputed.
|
|
952
|
+
update agp set v = -1 where id = 1;
|
|
953
|
+
select k, s from agp_sum order by k;
|
|
954
|
+
→ [{"k":1,"s":3},{"k":2,"s":8}]
|
|
955
|
+
|
|
956
|
+
-- the last in-scope row of group k=1 leaves scope → its backing row disappears.
|
|
957
|
+
update agp set v = -5 where id = 2;
|
|
958
|
+
select k, s from agp_sum order by k;
|
|
959
|
+
→ [{"k":2,"s":8}]
|
|
960
|
+
|
|
961
|
+
-- 22c. Multi-column group by (a, k) — composite group key as the backing key.
|
|
962
|
+
create table agm (id integer primary key, a integer, k integer, v integer);
|
|
963
|
+
insert into agm values (1, 1, 1, 10), (2, 1, 2, 20), (3, 2, 1, 30);
|
|
964
|
+
create materialized view agm_sum as select a, k, sum(v) as s from agm group by a, k;
|
|
965
|
+
select a, k, s from agm_sum order by a, k;
|
|
966
|
+
→ [{"a":1,"k":1,"s":10},{"a":1,"k":2,"s":20},{"a":2,"k":1,"s":30}]
|
|
967
|
+
|
|
968
|
+
-- key-changing update moves (a,k) from (1,1) to (2,1): old composite group emptied,
|
|
969
|
+
-- new composite group recomputed (regression guard for the multi-column delete key).
|
|
970
|
+
update agm set a = 2 where id = 1;
|
|
971
|
+
select a, k, s from agm_sum order by a, k;
|
|
972
|
+
→ [{"a":1,"k":2,"s":20},{"a":2,"k":1,"s":40}]
|
|
973
|
+
|
|
974
|
+
-- 22d. HAVING aggregate — the residual re-evaluates HAVING, so a group dropping below
|
|
975
|
+
-- the threshold returns zero rows and its backing row is removed; one rising to the
|
|
976
|
+
-- threshold gains a backing row. (Incidental but load-bearing — locks the behavior.)
|
|
977
|
+
create table agh (id integer primary key, k integer);
|
|
978
|
+
insert into agh values (1, 1), (2, 1), (3, 2);
|
|
979
|
+
create materialized view agh_dup as select k, count(*) as c from agh group by k having count(*) > 1;
|
|
980
|
+
-- only group k=1 (count 2) clears the HAVING threshold.
|
|
981
|
+
select k, c from agh_dup order by k;
|
|
982
|
+
→ [{"k":1,"c":2}]
|
|
983
|
+
|
|
984
|
+
-- group k=2 rises to count 2 → it now appears.
|
|
985
|
+
insert into agh values (4, 2);
|
|
986
|
+
select k, c from agh_dup order by k;
|
|
987
|
+
→ [{"k":1,"c":2},{"k":2,"c":2}]
|
|
988
|
+
|
|
989
|
+
-- group k=1 drops to count 1 → below HAVING → its backing row disappears.
|
|
990
|
+
delete from agh where id = 2;
|
|
991
|
+
select k, c from agh_dup order by k;
|
|
992
|
+
→ [{"k":2,"c":2}]
|
|
993
|
+
|
|
994
|
+
-- 22e. MV-over-MV with an aggregate producer. A recomputed group emits delete+upsert
|
|
995
|
+
-- effective changes (not a single update); a downstream covering-index consumer of
|
|
996
|
+
-- the aggregate backing must tolerate that and stay equivalent through insert,
|
|
997
|
+
-- key-change, and emptied-group cascades.
|
|
998
|
+
create table ags (id integer primary key, k integer, v integer);
|
|
999
|
+
insert into ags values (1, 1, 10), (2, 1, 5), (3, 2, 7);
|
|
1000
|
+
create materialized view ags_agg as select k, sum(v) as s from ags group by k;
|
|
1001
|
+
create materialized view ags_down as select k, s from ags_agg;
|
|
1002
|
+
select k, s from ags_down order by k;
|
|
1003
|
+
→ [{"k":1,"s":15},{"k":2,"s":7}]
|
|
1004
|
+
|
|
1005
|
+
-- insert recomputes group k=2 → cascades to the downstream MV.
|
|
1006
|
+
insert into ags values (4, 2, 100);
|
|
1007
|
+
select k, s from ags_down order by k;
|
|
1008
|
+
→ [{"k":1,"s":15},{"k":2,"s":107}]
|
|
1009
|
+
|
|
1010
|
+
-- key-changing update (id=3 from k=2 to k=1) recomputes both groups → cascades.
|
|
1011
|
+
update ags set k = 1 where id = 3;
|
|
1012
|
+
select k, s from ags_down order by k;
|
|
1013
|
+
→ [{"k":1,"s":22},{"k":2,"s":100}]
|
|
1014
|
+
|
|
1015
|
+
-- emptied group k=2 → its aggregate backing row is removed → downstream row disappears.
|
|
1016
|
+
delete from ags where k = 2;
|
|
1017
|
+
select k, s from ags_down order by k;
|
|
1018
|
+
→ [{"k":1,"s":22}]
|
|
1019
|
+
|
|
1020
|
+
-- ===================================
|
|
1021
|
+
-- 23. Single-source lateral-TVF fan-out bodies (`select T.pk…, f.* from T cross join
|
|
1022
|
+
-- lateral tvf(<args over T>) f`) — the prefix-delete arm. Each base row fans out to N
|
|
1023
|
+
-- backing rows keyed by the composite product key (base PK ∪ TVF key). Per source
|
|
1024
|
+
-- write, the base row's WHOLE fan-out slice is deleted by base-PK prefix and re-fanned
|
|
1025
|
+
-- from live state; an INSERT adds a slice, a base-PK-changing UPDATE moves the whole
|
|
1026
|
+
-- prefix, a DELETE removes the slice. (materialized-view-rowtime-prefix-delete-lateral-tvf)
|
|
1027
|
+
-- ===================================
|
|
1028
|
+
|
|
1029
|
+
create table lt (id integer primary key, x integer);
|
|
1030
|
+
insert into lt values (1, 2), (2, 3), (3, 0);
|
|
1031
|
+
create materialized view lt_fan as select lt.id, lt.x, f.value from lt cross join lateral generate_series(1, lt.x) f;
|
|
1032
|
+
|
|
1033
|
+
-- MV equals the body at create time: id=1 → {1,2}, id=2 → {1,2,3}, id=3 (x=0) → empty.
|
|
1034
|
+
select id, value from lt_fan order by id, value;
|
|
1035
|
+
→ [{"id":1,"value":1},{"id":1,"value":2},{"id":2,"value":1},{"id":2,"value":2},{"id":2,"value":3}]
|
|
1036
|
+
|
|
1037
|
+
-- INSERT adds a new base row's whole fan-out slice.
|
|
1038
|
+
insert into lt values (4, 2);
|
|
1039
|
+
select id, value from lt_fan order by id, value;
|
|
1040
|
+
→ [{"id":1,"value":1},{"id":1,"value":2},{"id":2,"value":1},{"id":2,"value":2},{"id":2,"value":3},{"id":4,"value":1},{"id":4,"value":2}]
|
|
1041
|
+
|
|
1042
|
+
-- UPDATE that GROWS the fan-out (x: 2 → 4): old slice deleted, new fan-out upserted.
|
|
1043
|
+
update lt set x = 4 where id = 1;
|
|
1044
|
+
select id, value from lt_fan where id = 1 order by value;
|
|
1045
|
+
→ [{"id":1,"value":1},{"id":1,"value":2},{"id":1,"value":3},{"id":1,"value":4}]
|
|
1046
|
+
|
|
1047
|
+
-- UPDATE that SHRINKS the fan-out (x: 3 → 1): stale tail rows removed by the prefix delete.
|
|
1048
|
+
update lt set x = 1 where id = 2;
|
|
1049
|
+
select id, value from lt_fan where id = 2 order by value;
|
|
1050
|
+
→ [{"id":2,"value":1}]
|
|
1051
|
+
|
|
1052
|
+
-- Base-PK-changing UPDATE moves the WHOLE prefix (id 4 → 7): old prefix gone, new fanned.
|
|
1053
|
+
update lt set id = 7 where id = 4;
|
|
1054
|
+
select id, value from lt_fan where id in (4, 7) order by id, value;
|
|
1055
|
+
→ [{"id":7,"value":1},{"id":7,"value":2}]
|
|
1056
|
+
|
|
1057
|
+
-- DELETE removes the whole fan-out slice.
|
|
1058
|
+
delete from lt where id = 1;
|
|
1059
|
+
select id, value from lt_fan where id = 1 order by value;
|
|
1060
|
+
→ []
|
|
1061
|
+
|
|
1062
|
+
-- Mid-transaction visibility + rollback reverts the slice in lockstep (reads-own-writes).
|
|
1063
|
+
begin;
|
|
1064
|
+
insert into lt values (9, 3);
|
|
1065
|
+
select id, value from lt_fan where id = 9 order by value;
|
|
1066
|
+
→ [{"id":9,"value":1},{"id":9,"value":2},{"id":9,"value":3}]
|
|
1067
|
+
rollback;
|
|
1068
|
+
select id, value from lt_fan where id = 9 order by value;
|
|
1069
|
+
→ []
|
|
1070
|
+
|
|
1071
|
+
-- An out-of-scope base row (partial WHERE) fans out to zero rows; a scope transition
|
|
1072
|
+
-- re-fans it. The body's WHERE is part of the residual, so maintenance tracks it.
|
|
1073
|
+
create table ltw (id integer primary key, x integer, k integer);
|
|
1074
|
+
insert into ltw values (1, 2, 9), (2, 3, 1);
|
|
1075
|
+
create materialized view ltw_fan as select ltw.id, g.value from ltw cross join lateral generate_series(1, ltw.x) g where ltw.k > 5;
|
|
1076
|
+
-- only id=1 (k=9 > 5) is in scope at create.
|
|
1077
|
+
select id, value from ltw_fan order by id, value;
|
|
1078
|
+
→ [{"id":1,"value":1},{"id":1,"value":2}]
|
|
1079
|
+
|
|
1080
|
+
-- id=2 enters scope (k: 1 → 7): its fan-out appears.
|
|
1081
|
+
update ltw set k = 7 where id = 2;
|
|
1082
|
+
select id, value from ltw_fan order by id, value;
|
|
1083
|
+
→ [{"id":1,"value":1},{"id":1,"value":2},{"id":2,"value":1},{"id":2,"value":2},{"id":2,"value":3}]
|
|
1084
|
+
|
|
1085
|
+
-- id=1 leaves scope (k: 9 → 2): its whole fan-out slice disappears.
|
|
1086
|
+
update ltw set k = 2 where id = 1;
|
|
1087
|
+
select id, value from ltw_fan order by id, value;
|
|
1088
|
+
→ [{"id":2,"value":1},{"id":2,"value":2},{"id":2,"value":3}]
|
|
1089
|
+
|
|
1090
|
+
-- ===================================
|
|
1091
|
+
-- 23.5 NOCASE base-PK lateral-TVF fan-out — the prefix-delete arm under a NON-binary base
|
|
1092
|
+
-- primary key. `delete-by-prefix` early-terminates its prefix scan on a BINARY compare
|
|
1093
|
+
-- (scan-layer.ts) while the backing btree orders the base-PK prefix by its NOCASE
|
|
1094
|
+
-- collation. Sound for this arm: the backing base-PK column inherits the source PK
|
|
1095
|
+
-- collation and source-PK uniqueness collapses each NOCASE class to one binary value, so
|
|
1096
|
+
-- a base row's fan-out rows are binary-homogeneous and contiguous. A case-only PK rewrite
|
|
1097
|
+
-- ('apple' → 'APPLE', the SAME PK under NOCASE) re-keys the whole slice to the new bytes.
|
|
1098
|
+
-- (prefix-delete-noncase-collation-regression-test)
|
|
1099
|
+
-- ===================================
|
|
1100
|
+
|
|
1101
|
+
create table ltc (id text collate NOCASE primary key, n integer);
|
|
1102
|
+
insert into ltc values ('apple', 2), ('Banana', 3), ('cherry', 0);
|
|
1103
|
+
create materialized view ltc_fan as select ltc.id, f.value from ltc cross join lateral generate_series(1, ltc.n) f;
|
|
1104
|
+
|
|
1105
|
+
-- MV equals the body at create: apple → {1,2}, Banana → {1,2,3}, cherry (n=0) → empty.
|
|
1106
|
+
select id, value from ltc_fan order by id, value;
|
|
1107
|
+
→ [{"id":"apple","value":1},{"id":"apple","value":2},{"id":"Banana","value":1},{"id":"Banana","value":2},{"id":"Banana","value":3}]
|
|
1108
|
+
|
|
1109
|
+
-- INSERT a new base slice whose binary value ('Date'=0x44) sorts BEFORE the existing
|
|
1110
|
+
-- NOCASE-earlier 'apple' (0x61) slice — the maintained backing must still place it correctly.
|
|
1111
|
+
insert into ltc values ('Date', 2);
|
|
1112
|
+
select id, value from ltc_fan where id = 'Date' order by value;
|
|
1113
|
+
→ [{"id":"Date","value":1},{"id":"Date","value":2}]
|
|
1114
|
+
|
|
1115
|
+
-- UPDATE GROWS the fan-out (n: 2 → 4): old slice deleted by prefix, new fan-out upserted.
|
|
1116
|
+
update ltc set n = 4 where id = 'apple';
|
|
1117
|
+
select id, value from ltc_fan where id = 'apple' order by value;
|
|
1118
|
+
→ [{"id":"apple","value":1},{"id":"apple","value":2},{"id":"apple","value":3},{"id":"apple","value":4}]
|
|
1119
|
+
|
|
1120
|
+
-- UPDATE SHRINKS the fan-out (n: 3 → 1): stale tail rows removed by the prefix delete.
|
|
1121
|
+
update ltc set n = 1 where id = 'Banana';
|
|
1122
|
+
select id, value from ltc_fan where id = 'Banana' order by value;
|
|
1123
|
+
→ [{"id":"Banana","value":1}]
|
|
1124
|
+
|
|
1125
|
+
-- A case-only base-PK rewrite ('apple' → 'APPLE') is the SAME PK under NOCASE, but rewrites
|
|
1126
|
+
-- the stored bytes the backing keys on: the old-byte slice is deleted by prefix and re-fanned
|
|
1127
|
+
-- under the new bytes. A NOCASE `where id = 'apple'` still finds it (now stored 'APPLE').
|
|
1128
|
+
update ltc set id = 'APPLE' where id = 'apple';
|
|
1129
|
+
select id, value from ltc_fan where id = 'apple' order by value;
|
|
1130
|
+
→ [{"id":"APPLE","value":1},{"id":"APPLE","value":2},{"id":"APPLE","value":3},{"id":"APPLE","value":4}]
|
|
1131
|
+
|
|
1132
|
+
-- DELETE removes the whole fan-out slice; a lowercase literal matches the stored 'Banana'
|
|
1133
|
+
-- under NOCASE.
|
|
1134
|
+
delete from ltc where id = 'banana';
|
|
1135
|
+
select id, value from ltc_fan where id = 'Banana' order by value;
|
|
1136
|
+
→ []
|
|
1137
|
+
|
|
1138
|
+
-- Mid-transaction visibility + rollback reverts the slice in lockstep (reads-own-writes).
|
|
1139
|
+
begin;
|
|
1140
|
+
insert into ltc values ('elder', 3);
|
|
1141
|
+
select id, value from ltc_fan where id = 'elder' order by value;
|
|
1142
|
+
→ [{"id":"elder","value":1},{"id":"elder","value":2},{"id":"elder","value":3}]
|
|
1143
|
+
rollback;
|
|
1144
|
+
select id, value from ltc_fan where id = 'elder' order by value;
|
|
1145
|
+
→ []
|
|
1146
|
+
|
|
1147
|
+
-- ===================================
|
|
1148
|
+
-- 24. 1:1 inner-join bodies (`select T.pk…, P.col from T join P on T.fk = P.id`) — the
|
|
1149
|
+
-- join-residual arm. T's NOT-NULL FK to P's PK makes the inner join provably 1:1 on T
|
|
1150
|
+
-- (no row loss via referential integrity, no fan-out via P's unique PK), so the backing
|
|
1151
|
+
-- is keyed on T's PK. A write to the driving table T recomputes its one joined row
|
|
1152
|
+
-- (forward, T-PK-keyed residual); a write to the lookup table P refreshes every joined
|
|
1153
|
+
-- row referencing it (reverse, P-PK-keyed residual, upsert-only — inner-join + RI
|
|
1154
|
+
-- membership is fixed by T.fk). (materialized-view-rowtime-residual-join)
|
|
1155
|
+
-- ===================================
|
|
1156
|
+
|
|
1157
|
+
create table jp (id integer primary key, name text);
|
|
1158
|
+
insert into jp values (1, 'alice'), (2, 'bob'), (3, 'carol');
|
|
1159
|
+
create table jt (id integer primary key, fk integer not null references jp(id));
|
|
1160
|
+
insert into jt values (10, 1), (20, 1), (30, 2);
|
|
1161
|
+
create materialized view jmv as select jt.id, jt.fk, jp.name from jt join jp on jt.fk = jp.id;
|
|
1162
|
+
|
|
1163
|
+
-- MV equals the body at create time (jt 10 & 20 both reference jp 1).
|
|
1164
|
+
select id, fk, name from jmv order by id;
|
|
1165
|
+
→ [{"id":10,"fk":1,"name":"alice"},{"id":20,"fk":1,"name":"alice"},{"id":30,"fk":2,"name":"bob"}]
|
|
1166
|
+
|
|
1167
|
+
-- INSERT a driving (jt) row → its one joined row appears (forward residual).
|
|
1168
|
+
insert into jt values (40, 3);
|
|
1169
|
+
select id, fk, name from jmv order by id;
|
|
1170
|
+
→ [{"id":10,"fk":1,"name":"alice"},{"id":20,"fk":1,"name":"alice"},{"id":30,"fk":2,"name":"bob"},{"id":40,"fk":3,"name":"carol"}]
|
|
1171
|
+
|
|
1172
|
+
-- UPDATE a driving row's FK → it re-joins to the new lookup row (reads-own-writes).
|
|
1173
|
+
update jt set fk = 2 where id = 10;
|
|
1174
|
+
select id, fk, name from jmv order by id;
|
|
1175
|
+
→ [{"id":10,"fk":2,"name":"bob"},{"id":20,"fk":1,"name":"alice"},{"id":30,"fk":2,"name":"bob"},{"id":40,"fk":3,"name":"carol"}]
|
|
1176
|
+
|
|
1177
|
+
-- LOOKUP-side UPDATE (jp.name) → every joined backing row referencing it is refreshed
|
|
1178
|
+
-- (reverse residual upserts each, no delete).
|
|
1179
|
+
update jp set name = 'BOB' where id = 2;
|
|
1180
|
+
select id, fk, name from jmv order by id;
|
|
1181
|
+
→ [{"id":10,"fk":2,"name":"BOB"},{"id":20,"fk":1,"name":"alice"},{"id":30,"fk":2,"name":"BOB"},{"id":40,"fk":3,"name":"carol"}]
|
|
1182
|
+
|
|
1183
|
+
-- DELETE a driving row → its joined backing row disappears (removed-join-row).
|
|
1184
|
+
delete from jt where id = 20;
|
|
1185
|
+
select id, fk, name from jmv order by id;
|
|
1186
|
+
→ [{"id":10,"fk":2,"name":"BOB"},{"id":30,"fk":2,"name":"BOB"},{"id":40,"fk":3,"name":"carol"}]
|
|
1187
|
+
|
|
1188
|
+
-- Key-changing UPDATE of the driving PK moves the backing row to the new key.
|
|
1189
|
+
update jt set id = 11 where id = 10;
|
|
1190
|
+
select id, fk, name from jmv order by id;
|
|
1191
|
+
→ [{"id":11,"fk":2,"name":"BOB"},{"id":30,"fk":2,"name":"BOB"},{"id":40,"fk":3,"name":"carol"}]
|
|
1192
|
+
|
|
1193
|
+
-- Mid-transaction visibility + rollback reverts both sides in lockstep.
|
|
1194
|
+
begin;
|
|
1195
|
+
insert into jp values (4, 'dave');
|
|
1196
|
+
insert into jt values (50, 4);
|
|
1197
|
+
select id, fk, name from jmv where id = 50;
|
|
1198
|
+
→ [{"id":50,"fk":4,"name":"dave"}]
|
|
1199
|
+
rollback;
|
|
1200
|
+
select id, fk, name from jmv where id = 50;
|
|
1201
|
+
→ []
|
|
1202
|
+
|
|
1203
|
+
-- A lookup INSERT with no referencing driving row is a no-op for the MV.
|
|
1204
|
+
insert into jp values (9, 'zara');
|
|
1205
|
+
select id, fk, name from jmv order by id;
|
|
1206
|
+
→ [{"id":11,"fk":2,"name":"BOB"},{"id":30,"fk":2,"name":"BOB"},{"id":40,"fk":3,"name":"carol"}]
|
|
1207
|
+
|
|
1208
|
+
-- A lookup DELETE of an unreferenced lookup row is likewise a no-op (upsert-only reverse
|
|
1209
|
+
-- residual: the deleted key joins no driving row, so the residual is empty — no backing row
|
|
1210
|
+
-- to refresh or remove). RI blocks deleting a referenced lookup row, so this is the only
|
|
1211
|
+
-- admissible lookup-delete shape.
|
|
1212
|
+
delete from jp where id = 9;
|
|
1213
|
+
select id, fk, name from jmv order by id;
|
|
1214
|
+
→ [{"id":11,"fk":2,"name":"BOB"},{"id":30,"fk":2,"name":"BOB"},{"id":40,"fk":3,"name":"carol"}]
|
|
1215
|
+
|
|
1216
|
+
-- ===================================
|
|
1217
|
+
-- 24.5. 1:1 inner-join bodies WITH a partial WHERE (mv-join-where-widening). The join-residual
|
|
1218
|
+
-- arm now accepts a body WHERE, classified by which base table(s) it references:
|
|
1219
|
+
-- - a WHERE over the driving table T only (`where wta.amt > 5`) just relaxes the gate —
|
|
1220
|
+
-- the forward T-keyed residual already injects + applies it (an out-of-scope T row
|
|
1221
|
+
-- recomputes to zero rows ⇒ removed), and the lookup side stays UPSERT-ONLY because a
|
|
1222
|
+
-- T-column predicate cannot move the membership set {T : T.fk = P.id};
|
|
1223
|
+
-- - a WHERE referencing the lookup P (`where wpb.score > 5`) makes the lookup side
|
|
1224
|
+
-- DELETE-CAPABLE: per affected P key the membership residual (WHERE stripped) deletes
|
|
1225
|
+
-- every currently-referencing T.pk backing key, then the in-scope residual re-upserts
|
|
1226
|
+
-- the survivors — so a P write that flips a row's WHERE membership adds/removes it.
|
|
1227
|
+
-- ===================================
|
|
1228
|
+
|
|
1229
|
+
-- --- T-only WHERE: forward residual handles T-side scope flips; lookup stays upsert-only. ---
|
|
1230
|
+
create table wpa (id integer primary key, name text);
|
|
1231
|
+
create table wta (id integer primary key, fk integer not null references wpa(id), amt integer);
|
|
1232
|
+
insert into wpa values (1, 'alice'), (2, 'bob');
|
|
1233
|
+
insert into wta values (10, 1, 2), (20, 1, 8), (30, 2, 4);
|
|
1234
|
+
create materialized view mvwa as select wta.id, wta.amt, wpa.name from wta join wpa on wta.fk = wpa.id where wta.amt > 5;
|
|
1235
|
+
|
|
1236
|
+
-- Create time: only wta 20 (amt 8 > 5) is in scope.
|
|
1237
|
+
select id, amt, name from mvwa order by id;
|
|
1238
|
+
→ [{"id":20,"amt":8,"name":"alice"}]
|
|
1239
|
+
|
|
1240
|
+
-- A driving-row update that moves it INTO the T-side scope (amt 2 → 9) adds its joined row.
|
|
1241
|
+
update wta set amt = 9 where id = 10;
|
|
1242
|
+
select id, amt, name from mvwa order by id;
|
|
1243
|
+
→ [{"id":10,"amt":9,"name":"alice"},{"id":20,"amt":8,"name":"alice"}]
|
|
1244
|
+
|
|
1245
|
+
-- A driving-row update that moves it OUT of scope (amt 8 → 1) removes its backing row (the
|
|
1246
|
+
-- forward residual recomputes to zero rows, so the delete-without-upsert removes it).
|
|
1247
|
+
update wta set amt = 1 where id = 20;
|
|
1248
|
+
select id, amt, name from mvwa order by id;
|
|
1249
|
+
→ [{"id":10,"amt":9,"name":"alice"}]
|
|
1250
|
+
|
|
1251
|
+
-- A lookup (wpa) name update refreshes the projected column of the in-scope joined row — no
|
|
1252
|
+
-- add/remove (upsert-only; a T-only predicate cannot move membership on a P write).
|
|
1253
|
+
update wpa set name = 'ALICE' where id = 1;
|
|
1254
|
+
select id, amt, name from mvwa order by id;
|
|
1255
|
+
→ [{"id":10,"amt":9,"name":"ALICE"}]
|
|
1256
|
+
|
|
1257
|
+
-- --- P-referencing WHERE: the lookup side is delete-capable (membership flips). ---
|
|
1258
|
+
create table wpb (id integer primary key, name text, score integer);
|
|
1259
|
+
create table wtb (id integer primary key, fk integer not null references wpb(id));
|
|
1260
|
+
insert into wpb values (1, 'alice', 3), (2, 'bob', 7);
|
|
1261
|
+
insert into wtb values (10, 1), (20, 1), (30, 2);
|
|
1262
|
+
create materialized view mvwb as select wtb.id, wtb.fk, wpb.name, wpb.score from wtb join wpb on wtb.fk = wpb.id where wpb.score > 5;
|
|
1263
|
+
|
|
1264
|
+
-- Create time: only rows joined to a wpb with score > 5 (wpb 2). wtb 30 (fk 2).
|
|
1265
|
+
select id, fk, name, score from mvwb order by id;
|
|
1266
|
+
→ [{"id":30,"fk":2,"name":"bob","score":7}]
|
|
1267
|
+
|
|
1268
|
+
-- A lookup update that pulls wpb 1 INTO scope (score 3 → 8): the two wtb rows referencing it
|
|
1269
|
+
-- (10, 20) gain backing rows (membership residual delete is a no-op; in-scope residual upserts).
|
|
1270
|
+
update wpb set score = 8 where id = 1;
|
|
1271
|
+
select id, fk, name, score from mvwb order by id;
|
|
1272
|
+
→ [{"id":10,"fk":1,"name":"alice","score":8},{"id":20,"fk":1,"name":"alice","score":8},{"id":30,"fk":2,"name":"bob","score":7}]
|
|
1273
|
+
|
|
1274
|
+
-- A lookup update that pushes wpb 2 OUT of scope (score 7 → 2): wtb 30 is removed — the delete
|
|
1275
|
+
-- pass deletes its backing key and the in-scope residual returns nothing (upsert-only could not).
|
|
1276
|
+
update wpb set score = 2 where id = 2;
|
|
1277
|
+
select id, fk, name, score from mvwb order by id;
|
|
1278
|
+
→ [{"id":10,"fk":1,"name":"alice","score":8},{"id":20,"fk":1,"name":"alice","score":8}]
|
|
1279
|
+
|
|
1280
|
+
-- A driving FK-move re-evaluates the P-side predicate against the NEW p: move wtb 10 to fk 2
|
|
1281
|
+
-- (wpb 2 is now score 2, out of scope) → its joined row disappears (the forward residual
|
|
1282
|
+
-- applies the full WHERE, including the p column, against the live joined p).
|
|
1283
|
+
update wtb set fk = 2 where id = 10;
|
|
1284
|
+
select id, fk, name, score from mvwb order by id;
|
|
1285
|
+
→ [{"id":20,"fk":1,"name":"alice","score":8}]
|
|
1286
|
+
|
|
1287
|
+
-- An in-scope lookup payload update (wpb 1 stays score 8): the delete-then-upsert refreshes the
|
|
1288
|
+
-- projected name of its still-referencing row (wtb 20) without adding/removing.
|
|
1289
|
+
update wpb set name = 'ALICE' where id = 1;
|
|
1290
|
+
select id, fk, name, score from mvwb order by id;
|
|
1291
|
+
→ [{"id":20,"fk":1,"name":"ALICE","score":8}]
|
|
1292
|
+
|
|
1293
|
+
-- Mid-transaction membership flip + rollback reverts the delete-capable pass in lockstep.
|
|
1294
|
+
-- wpb 2 score 2 → 9 pulls its two referencing rows (wtb 10, 30 — both now fk 2) into scope.
|
|
1295
|
+
begin;
|
|
1296
|
+
update wpb set score = 9 where id = 2;
|
|
1297
|
+
select id, fk, name, score from mvwb where fk = 2 order by id;
|
|
1298
|
+
→ [{"id":10,"fk":2,"name":"bob","score":9},{"id":30,"fk":2,"name":"bob","score":9}]
|
|
1299
|
+
rollback;
|
|
1300
|
+
select id, fk, name, score from mvwb order by id;
|
|
1301
|
+
→ [{"id":20,"fk":1,"name":"ALICE","score":8}]
|
|
1302
|
+
|
|
1303
|
+
-- ===================================
|
|
1304
|
+
-- 25. End-of-statement flush boundary (mv-statement-flush-deferral). The DML executor now
|
|
1305
|
+
-- drains a per-statement deferred-rebuild set at the end-of-statement savepoint
|
|
1306
|
+
-- boundary — but ONLY the FULL-REBUILD arm is ever deferred there. Every arm above is
|
|
1307
|
+
-- a BOUNDED-DELTA arm, applied per source row immediately, so for these the deferred set
|
|
1308
|
+
-- stays empty and the flush is a transparent no-op. The full-rebuild arm is now reachable
|
|
1309
|
+
-- from `create materialized view` (the eligibility flip — this ticket); §26 below exercises
|
|
1310
|
+
-- its deferred flush end-to-end under SQL writes, while its instrumented rebuild-count, atomic
|
|
1311
|
+
-- rollback, autocommit flush+commit, mixed-arm, and MV-over-MV mixed-arm behavior stays
|
|
1312
|
+
-- white-box in test/incremental/maintenance-equivalence.spec.ts (forceFullRebuild + a counter).
|
|
1313
|
+
--
|
|
1314
|
+
-- The cases below regression-guard that adding the deferred set + flush hook left the
|
|
1315
|
+
-- bounded-delta arms untouched: a bulk multi-row statement that also crosses a partial
|
|
1316
|
+
-- predicate inside a 2-level MV-over-MV chain still converges at every level, with the
|
|
1317
|
+
-- flush running (empty) after the row loop and before the statement commits.
|
|
1318
|
+
-- ===================================
|
|
1319
|
+
|
|
1320
|
+
create table fb (id integer primary key, x integer);
|
|
1321
|
+
insert into fb values (1, 5), (2, -3), (3, 8);
|
|
1322
|
+
create materialized view fb1 as select id, x from fb where x > 0;
|
|
1323
|
+
create materialized view fb2 as select id, x from fb1;
|
|
1324
|
+
select id, x from fb2 order by id;
|
|
1325
|
+
→ [{"id":1,"x":5},{"id":3,"x":8}]
|
|
1326
|
+
|
|
1327
|
+
-- ONE bulk UPDATE moves id=1 OUT of the inner predicate and id=2 INTO it, in a single
|
|
1328
|
+
-- statement: row-by-row maintenance applies during the loop, the (empty) full-rebuild flush
|
|
1329
|
+
-- is a no-op, and BOTH chain levels reflect the net result after the statement commits.
|
|
1330
|
+
update fb set x = case when id = 1 then -1 when id = 2 then 7 else x end;
|
|
1331
|
+
select id, x from fb1 order by id;
|
|
1332
|
+
→ [{"id":2,"x":7},{"id":3,"x":8}]
|
|
1333
|
+
select id, x from fb2 order by id;
|
|
1334
|
+
→ [{"id":2,"x":7},{"id":3,"x":8}]
|
|
1335
|
+
|
|
1336
|
+
-- A bulk DELETE in one statement cascades through both levels; the post-loop flush is a no-op.
|
|
1337
|
+
delete from fb where id in (2, 3);
|
|
1338
|
+
select id, x from fb2 order by id;
|
|
1339
|
+
→ []
|
|
1340
|
+
|
|
1341
|
+
-- ===================================
|
|
1342
|
+
-- 26. Full-rebuild floor maintenance under SQL writes (mv-eligibility-floor-fallthrough). A body
|
|
1343
|
+
-- that fits no bounded-delta arm — here a 2-source UNION — is maintained by the always-correct
|
|
1344
|
+
-- full-rebuild floor: re-evaluated wholesale at the end-of-statement flush, then `replace-all`
|
|
1345
|
+
-- onto the backing. insert / update / delete on EITHER source must reflect through the MV after
|
|
1346
|
+
-- each statement commits. (The deferred-flush internals — one rebuild per bulk statement,
|
|
1347
|
+
-- atomic rollback, mixed-arm — are white-box in maintenance-equivalence.spec.ts; this is the
|
|
1348
|
+
-- end-to-end SQL proof that the floor is now create-reachable and maintained.)
|
|
1349
|
+
-- ===================================
|
|
1350
|
+
|
|
1351
|
+
create table u1 (id integer primary key, v integer);
|
|
1352
|
+
create table u2 (id integer primary key, v integer);
|
|
1353
|
+
insert into u1 values (1, 10), (2, 20);
|
|
1354
|
+
insert into u2 values (3, 30);
|
|
1355
|
+
create materialized view u_all as select id, v from u1 union select id, v from u2;
|
|
1356
|
+
select id, v from u_all order by id, v;
|
|
1357
|
+
→ [{"id":1,"v":10},{"id":2,"v":20},{"id":3,"v":30}]
|
|
1358
|
+
|
|
1359
|
+
-- insert into the SECOND source → floor rebuild picks it up.
|
|
1360
|
+
insert into u2 values (4, 40);
|
|
1361
|
+
select id, v from u_all order by id, v;
|
|
1362
|
+
→ [{"id":1,"v":10},{"id":2,"v":20},{"id":3,"v":30},{"id":4,"v":40}]
|
|
1363
|
+
|
|
1364
|
+
-- update a row in the FIRST source → the rebuilt body reflects the new value.
|
|
1365
|
+
update u1 set v = 99 where id = 1;
|
|
1366
|
+
select id, v from u_all order by id, v;
|
|
1367
|
+
→ [{"id":1,"v":99},{"id":2,"v":20},{"id":3,"v":30},{"id":4,"v":40}]
|
|
1368
|
+
|
|
1369
|
+
-- delete from the second source → the row leaves the rebuilt body.
|
|
1370
|
+
delete from u2 where id = 3;
|
|
1371
|
+
select id, v from u_all order by id, v;
|
|
1372
|
+
→ [{"id":1,"v":99},{"id":2,"v":20},{"id":4,"v":40}]
|
|
1373
|
+
|
|
1374
|
+
-- a bulk multi-row UPDATE on the first source in one statement → all reflected after commit.
|
|
1375
|
+
update u1 set v = v + 1;
|
|
1376
|
+
select id, v from u_all order by id, v;
|
|
1377
|
+
→ [{"id":1,"v":100},{"id":2,"v":21},{"id":4,"v":40}]
|
|
1378
|
+
|
|
1379
|
+
-- ===================================
|
|
1380
|
+
-- 27. Full-rebuild → full-rebuild CHAIN (mv-comprehensive-coverage-net). Two chained floor MVs:
|
|
1381
|
+
-- a write to the base dirties only the producer (round 1); the producer's rebuild emits a
|
|
1382
|
+
-- delta that re-dirties the full-rebuild consumer, rebuilt in round 2 — the multi-round
|
|
1383
|
+
-- worklist convergence that only became SQL-buildable after the eligibility flip. ch_c
|
|
1384
|
+
-- distinct-a over ch_p (a is NOT unique in ch_p) is genuinely full-rebuild, not collapsed to
|
|
1385
|
+
-- a passthrough. (Round counts are white-box in maintenance-equivalence.spec.ts; this is the
|
|
1386
|
+
-- end-to-end SQL proof both levels converge.)
|
|
1387
|
+
-- ===================================
|
|
1388
|
+
|
|
1389
|
+
create table ch (id integer primary key, a integer, b integer);
|
|
1390
|
+
insert into ch (id, a, b) values (1, 10, 100), (2, 10, 200), (3, 20, 100);
|
|
1391
|
+
create materialized view ch_p as select distinct a, b from ch;
|
|
1392
|
+
create materialized view ch_c as select distinct a from ch_p;
|
|
1393
|
+
select a, b from ch_p order by a, b;
|
|
1394
|
+
→ [{"a":10,"b":100},{"a":10,"b":200},{"a":20,"b":100}]
|
|
1395
|
+
select a from ch_c order by a;
|
|
1396
|
+
→ [{"a":10},{"a":20}]
|
|
1397
|
+
|
|
1398
|
+
-- a write introducing a NEW `a` value propagates producer → consumer (drives the round-2 flush).
|
|
1399
|
+
insert into ch (id, a, b) values (4, 30, 100);
|
|
1400
|
+
select a from ch_c order by a;
|
|
1401
|
+
→ [{"a":10},{"a":20},{"a":30}]
|
|
1402
|
+
|
|
1403
|
+
-- moving the only a=20 row to a=30 removes a=20 from the consumer (producer's set shrank).
|
|
1404
|
+
update ch set a = 30 where id = 3;
|
|
1405
|
+
select a from ch_c order by a;
|
|
1406
|
+
→ [{"a":10},{"a":30}]
|
|
1407
|
+
|
|
1408
|
+
-- delete cascades through both levels.
|
|
1409
|
+
delete from ch where a = 10;
|
|
1410
|
+
select a, b from ch_p order by a, b;
|
|
1411
|
+
→ [{"a":30,"b":100}]
|
|
1412
|
+
select a from ch_c order by a;
|
|
1413
|
+
→ [{"a":30}]
|
|
1414
|
+
|
|
1415
|
+
-- ===================================
|
|
1416
|
+
-- 28. Recursive-CTE transitive closure (mv-comprehensive-coverage-net). A recursive body fits no
|
|
1417
|
+
-- bounded-delta arm and reads a real source (`tcedge`), so it routes to the full-rebuild floor
|
|
1418
|
+
-- and is rebuilt wholesale per writing statement. Edge churn grows / shrinks the closure.
|
|
1419
|
+
-- ===================================
|
|
1420
|
+
|
|
1421
|
+
create table tcedge (src integer, dst integer, primary key (src, dst));
|
|
1422
|
+
insert into tcedge (src, dst) values (1, 2), (2, 3);
|
|
1423
|
+
create materialized view tc_mv as
|
|
1424
|
+
with recursive tc(a, b) as (
|
|
1425
|
+
select src, dst from tcedge
|
|
1426
|
+
union
|
|
1427
|
+
select t.a, e.dst from tc t join tcedge e on t.b = e.src
|
|
1428
|
+
) select a, b from tc;
|
|
1429
|
+
select a, b from tc_mv order by a, b;
|
|
1430
|
+
→ [{"a":1,"b":2},{"a":1,"b":3},{"a":2,"b":3}]
|
|
1431
|
+
|
|
1432
|
+
-- extend the chain 3→4: new multi-hop reachable pairs appear at every prefix.
|
|
1433
|
+
insert into tcedge (src, dst) values (3, 4);
|
|
1434
|
+
select a, b from tc_mv order by a, b;
|
|
1435
|
+
→ [{"a":1,"b":2},{"a":1,"b":3},{"a":1,"b":4},{"a":2,"b":3},{"a":2,"b":4},{"a":3,"b":4}]
|
|
1436
|
+
|
|
1437
|
+
-- remove the middle edge 2→3: every path through it vanishes from the closure.
|
|
1438
|
+
delete from tcedge where src = 2 and dst = 3;
|
|
1439
|
+
select a, b from tc_mv order by a, b;
|
|
1440
|
+
→ [{"a":1,"b":2},{"a":3,"b":4}]
|
|
1441
|
+
|
|
1442
|
+
-- ===================================
|
|
1443
|
+
-- 29. OR FAIL over a full-rebuild MV (mv-comprehensive-coverage-net). OR FAIL keeps the rows that
|
|
1444
|
+
-- already succeeded (no statement-scope savepoint wraps them), so a mid-statement abort leaves
|
|
1445
|
+
-- them in place — and the deferred full-rebuild flush must still run on the abort path, or the
|
|
1446
|
+
-- floor backing would lag the surviving rows mid-transaction. This pins that end-to-end: the MV
|
|
1447
|
+
-- reflects the kept rows after the abort, and a rollback reverts source + backing in lockstep.
|
|
1448
|
+
-- ===================================
|
|
1449
|
+
|
|
1450
|
+
create table ff (id integer primary key, v integer);
|
|
1451
|
+
insert into ff (id, v) values (1, 5);
|
|
1452
|
+
create materialized view ff_mv as select distinct v from ff;
|
|
1453
|
+
select v from ff_mv order by v;
|
|
1454
|
+
→ [{"v":5}]
|
|
1455
|
+
|
|
1456
|
+
begin;
|
|
1457
|
+
-- (10,9) succeeds and dirties the floor MV; (1,9) collides on the PK and FAILs the statement;
|
|
1458
|
+
-- (11,11) never runs.
|
|
1459
|
+
insert or fail into ff (id, v) values (10, 9), (1, 9), (11, 11);
|
|
1460
|
+
-- error: UNIQUE constraint failed
|
|
1461
|
+
-- the surviving source row is kept (OR FAIL does not unwind prior rows)…
|
|
1462
|
+
select id, v from ff order by id;
|
|
1463
|
+
→ [{"id":1,"v":5},{"id":10,"v":9}]
|
|
1464
|
+
-- …and the deferred full-rebuild flushed on the abort path, so the MV is NOT left stale at [5].
|
|
1465
|
+
select v from ff_mv order by v;
|
|
1466
|
+
→ [{"v":5},{"v":9}]
|
|
1467
|
+
rollback;
|
|
1468
|
+
-- the whole transaction reverts source + backing in lockstep.
|
|
1469
|
+
select v from ff_mv order by v;
|
|
1470
|
+
→ [{"v":5}]
|
|
1471
|
+
|
|
1472
|
+
-- ===================================
|
|
1473
|
+
-- 30. Outer (left) join MV — nullable lookup column read-side folding
|
|
1474
|
+
-- (mv-outer-join-nullable-backing-isnull). The null-extended lookup column of a LEFT
|
|
1475
|
+
-- join must be stamped NULLABLE on the backing table, so a query against the MV with
|
|
1476
|
+
-- `name is null` / `is not null` null-checks at runtime instead of folding against a
|
|
1477
|
+
-- bogus NOT-NULL backing column. The MV must be indistinguishable from the equivalent
|
|
1478
|
+
-- plain view — including these predicate reads, which the full `select *` read never
|
|
1479
|
+
-- exercises.
|
|
1480
|
+
-- ===================================
|
|
1481
|
+
|
|
1482
|
+
create table ojp (id integer primary key, name text);
|
|
1483
|
+
create table ojt (id integer primary key, fk integer);
|
|
1484
|
+
insert into ojp (id, name) values (1, 'a'), (3, 'c');
|
|
1485
|
+
-- fk=9 (id=2) references no ojp row → null-extended by the left join.
|
|
1486
|
+
insert into ojt (id, fk) values (1, 1), (2, 9), (3, 3);
|
|
1487
|
+
create materialized view oj_mv as select ojt.id, ojt.fk, ojp.name from ojt left join ojp on ojt.fk = ojp.id;
|
|
1488
|
+
|
|
1489
|
+
-- full scan: the unmatched row carries a NULL lookup column.
|
|
1490
|
+
select id, fk, name from oj_mv order by id;
|
|
1491
|
+
→ [{"id":1,"fk":1,"name":"a"},{"id":2,"fk":9,"name":null},{"id":3,"fk":3,"name":"c"}]
|
|
1492
|
+
|
|
1493
|
+
-- `name is null` returns the null-extended row (would be [] folded against a NOT-NULL backing).
|
|
1494
|
+
select id from oj_mv where name is null;
|
|
1495
|
+
→ [{"id":2}]
|
|
1496
|
+
|
|
1497
|
+
-- `name is not null` returns only the matched rows (would be all rows folded against NOT-NULL).
|
|
1498
|
+
select id from oj_mv where name is not null order by id;
|
|
1499
|
+
→ [{"id":1},{"id":3}]
|
|
1500
|
+
|
|
1501
|
+
-- equality is unaffected (it does not use the null fold).
|
|
1502
|
+
select id from oj_mv where name = 'a';
|
|
1503
|
+
→ [{"id":1}]
|
|
1504
|
+
|
|
1505
|
+
-- a source write that null-extends another row is reflected through the predicate read: deleting
|
|
1506
|
+
-- the only matching ojp (id=1) null-extends ojt id=1 as well.
|
|
1507
|
+
delete from ojp where id = 1;
|
|
1508
|
+
select id from oj_mv where name is null order by id;
|
|
1509
|
+
→ [{"id":1},{"id":2}]
|
|
1510
|
+
select id from oj_mv where name is not null;
|
|
1511
|
+
→ [{"id":3}]
|
|
1512
|
+
|
|
1513
|
+
-- ===================================
|
|
1514
|
+
-- 31. Outer (right) join MV — the symmetric case: the null-extended side is the
|
|
1515
|
+
-- PRESERVED-against side AND carries the join's other PK. A `right join` keyed on the
|
|
1516
|
+
-- preserved right table null-extends the LEFT columns (including the left PK), which the
|
|
1517
|
+
-- projection-layer fix must likewise stamp nullable on the backing — not just a plain
|
|
1518
|
+
-- lookup column. (A `full` join has no provable key → bag → not materializable, so the
|
|
1519
|
+
-- right join is the materializable both-sided-null-extension analog.)
|
|
1520
|
+
-- ===================================
|
|
1521
|
+
|
|
1522
|
+
create table rjl (id integer primary key, name text);
|
|
1523
|
+
create table rjr (id integer primary key, lid integer);
|
|
1524
|
+
insert into rjl (id, name) values (1, 'a'), (3, 'c');
|
|
1525
|
+
-- lid=9 (rjr id=20) references no rjl row → the LEFT side (name, left PK) is null-extended.
|
|
1526
|
+
insert into rjr (id, lid) values (10, 1), (20, 9), (30, 3);
|
|
1527
|
+
create materialized view rj_mv as select rjr.id as rid, rjr.lid, rjl.name, rjl.id as lid_pk from rjl right join rjr on rjr.lid = rjl.id;
|
|
1528
|
+
|
|
1529
|
+
-- full scan: the unmatched right row null-extends BOTH the lookup column and the left PK.
|
|
1530
|
+
select rid, lid, name, lid_pk from rj_mv order by rid;
|
|
1531
|
+
→ [{"rid":10,"lid":1,"name":"a","lid_pk":1},{"rid":20,"lid":9,"name":null,"lid_pk":null},{"rid":30,"lid":3,"name":"c","lid_pk":3}]
|
|
1532
|
+
|
|
1533
|
+
-- `name is null` over the null-extended lookup column reads at runtime (not folded to []).
|
|
1534
|
+
select rid from rj_mv where name is null;
|
|
1535
|
+
→ [{"rid":20}]
|
|
1536
|
+
|
|
1537
|
+
-- `lid_pk is null` over the null-extended LEFT PK — the column a base-table PK stamps NOT NULL —
|
|
1538
|
+
-- must also read at runtime, the symmetric guard the left-join case cannot exercise.
|
|
1539
|
+
select rid from rj_mv where lid_pk is null;
|
|
1540
|
+
→ [{"rid":20}]
|
|
1541
|
+
|
|
1542
|
+
select rid from rj_mv where name is not null order by rid;
|
|
1543
|
+
→ [{"rid":10},{"rid":30}]
|
|
1544
|
+
|
|
1545
|
+
-- ===================================
|
|
1546
|
+
-- 32. Row-time maintenance sees the STORED source row, not the raw proposed one
|
|
1547
|
+
-- (bug-dml-downstream-uses-uncoerced-row). The storage layer coerces a write
|
|
1548
|
+
-- to the declared column type inside `vtab.update()` — a `json` column given
|
|
1549
|
+
-- the text '{ "a" : 1 }' stores the same JSON value as '{"a":1}'. The DML
|
|
1550
|
+
-- executor used to hand maintenance the PRE-coercion row, so the
|
|
1551
|
+
-- incrementally maintained MV grouped the two spellings into two groups
|
|
1552
|
+
-- while the same body evaluated over the base table produced one. The
|
|
1553
|
+
-- maintained MV must be indistinguishable from the equivalent plain view.
|
|
1554
|
+
-- ===================================
|
|
1555
|
+
|
|
1556
|
+
create table jg (id integer primary key, j json, v integer);
|
|
1557
|
+
create materialized view jg_mv as select j, count(*) as c, sum(v) as s from jg group by j;
|
|
1558
|
+
-- the same body as a plain (always-recomputed-from-base) view, for parity
|
|
1559
|
+
create view jg_plain as select j, count(*) as c, sum(v) as s from jg group by j;
|
|
1560
|
+
|
|
1561
|
+
-- two structurally-equal, textually-DIFFERENT JSON spellings → ONE group of 2.
|
|
1562
|
+
insert into jg values (1, '{"a":1}', 10);
|
|
1563
|
+
insert into jg values (2, '{ "a" : 1 }', 5);
|
|
1564
|
+
|
|
1565
|
+
select j, c, s from jg_mv order by c;
|
|
1566
|
+
→ [{"j":{"a":1},"c":2,"s":15}]
|
|
1567
|
+
|
|
1568
|
+
-- ...and the plain view agrees (this is the assertion the maintained MV used to fail).
|
|
1569
|
+
select j, c, s from jg_plain order by c;
|
|
1570
|
+
→ [{"j":{"a":1},"c":2,"s":15}]
|
|
1571
|
+
|
|
1572
|
+
-- a genuinely different JSON value opens its own group.
|
|
1573
|
+
insert into jg values (3, '{"a":2}', 7);
|
|
1574
|
+
select c, s from jg_mv order by s;
|
|
1575
|
+
→ [{"c":1,"s":7},{"c":2,"s":15}]
|
|
1576
|
+
select c, s from jg_plain order by s;
|
|
1577
|
+
→ [{"c":1,"s":7},{"c":2,"s":15}]
|
|
1578
|
+
|
|
1579
|
+
-- a group-key UPDATE whose new key is a whitespace variant of an EXISTING group
|
|
1580
|
+
-- must merge into it (raw-row maintenance would have opened a third group).
|
|
1581
|
+
update jg set j = '{ "a":1 }' where id = 3;
|
|
1582
|
+
select c, s from jg_mv order by s;
|
|
1583
|
+
→ [{"c":3,"s":22}]
|
|
1584
|
+
select c, s from jg_plain order by s;
|
|
1585
|
+
→ [{"c":3,"s":22}]
|
|
1586
|
+
|
|
1587
|
+
-- ...and splitting back out again removes the merged contribution.
|
|
1588
|
+
update jg set j = '{"a":3}' where id = 3;
|
|
1589
|
+
select c, s from jg_mv order by s;
|
|
1590
|
+
→ [{"c":1,"s":7},{"c":2,"s":15}]
|
|
1591
|
+
select c, s from jg_plain order by s;
|
|
1592
|
+
→ [{"c":1,"s":7},{"c":2,"s":15}]
|
|
1593
|
+
|
|
1594
|
+
-- an INTEGER-affinity group key behaves the same: '7' stores as 7 and must group
|
|
1595
|
+
-- with a numeric 7 rather than beside it.
|
|
1596
|
+
create table ng (id integer primary key, k integer, v integer);
|
|
1597
|
+
create materialized view ng_mv as select k, count(*) as c from ng group by k;
|
|
1598
|
+
insert into ng values (1, 7, 1);
|
|
1599
|
+
insert into ng values (2, '7', 2);
|
|
1600
|
+
select k, c from ng_mv order by k;
|
|
1601
|
+
→ [{"k":7,"c":2}]
|