@quereus/quereus 4.20.1 → 4.20.3
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/schema/schema-differ.d.ts.map +1 -1
- package/dist/src/schema/schema-differ.js +17 -69
- package/dist/src/schema/schema-differ.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 +210 -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,1417 @@
|
|
|
1
|
+
-- Testing CREATE ASSERTION and DROP ASSERTION
|
|
2
|
+
|
|
3
|
+
-- ===================================
|
|
4
|
+
-- Basic DDL Round-Trip
|
|
5
|
+
-- ===================================
|
|
6
|
+
|
|
7
|
+
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
|
|
8
|
+
CREATE ASSERTION always_passes CHECK (1 = 1);
|
|
9
|
+
INSERT INTO users VALUES (1, 'Alice');
|
|
10
|
+
COMMIT;
|
|
11
|
+
DROP ASSERTION always_passes;
|
|
12
|
+
INSERT INTO users VALUES (2, 'Bob');
|
|
13
|
+
COMMIT;
|
|
14
|
+
SELECT name FROM users ORDER BY id;
|
|
15
|
+
→ [{ "name": "Alice" }, { "name": "Bob" }]
|
|
16
|
+
|
|
17
|
+
DROP TABLE users;
|
|
18
|
+
-- run
|
|
19
|
+
|
|
20
|
+
-- ===================================
|
|
21
|
+
-- Assertion Violation at COMMIT
|
|
22
|
+
-- ===================================
|
|
23
|
+
|
|
24
|
+
CREATE TABLE numbers (value INTEGER PRIMARY KEY);
|
|
25
|
+
CREATE ASSERTION always_fails CHECK (1 = 0);
|
|
26
|
+
-- run
|
|
27
|
+
|
|
28
|
+
-- Try to commit with the failing assertion
|
|
29
|
+
BEGIN;
|
|
30
|
+
INSERT INTO numbers VALUES (5);
|
|
31
|
+
COMMIT;
|
|
32
|
+
-- error: Integrity assertion failed: always_fails
|
|
33
|
+
|
|
34
|
+
-- Verify nothing was committed
|
|
35
|
+
SELECT COUNT(*) FROM numbers;
|
|
36
|
+
→ [{ "count(*)": 0 }]
|
|
37
|
+
|
|
38
|
+
-- Drop the failing assertion
|
|
39
|
+
DROP ASSERTION always_fails;
|
|
40
|
+
-- run
|
|
41
|
+
|
|
42
|
+
-- Now we can insert data
|
|
43
|
+
INSERT INTO numbers VALUES (5);
|
|
44
|
+
COMMIT;
|
|
45
|
+
SELECT value FROM numbers;
|
|
46
|
+
→ [{ "value": 5 }]
|
|
47
|
+
|
|
48
|
+
DROP TABLE numbers;
|
|
49
|
+
-- run
|
|
50
|
+
|
|
51
|
+
-- ===================================
|
|
52
|
+
-- Single-Table CHECK-like Assertion
|
|
53
|
+
-- ===================================
|
|
54
|
+
|
|
55
|
+
CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER);
|
|
56
|
+
INSERT INTO accounts VALUES (1, 100);
|
|
57
|
+
-- run
|
|
58
|
+
|
|
59
|
+
-- Balance must be non-negative
|
|
60
|
+
CREATE ASSERTION positive_balance CHECK (NOT EXISTS (SELECT 1 FROM accounts WHERE balance < 0));
|
|
61
|
+
-- run
|
|
62
|
+
|
|
63
|
+
-- Valid update: stays positive
|
|
64
|
+
BEGIN;
|
|
65
|
+
UPDATE accounts SET balance = 50 WHERE id = 1;
|
|
66
|
+
COMMIT;
|
|
67
|
+
-- run
|
|
68
|
+
|
|
69
|
+
SELECT balance FROM accounts WHERE id = 1;
|
|
70
|
+
→ [{ "balance": 50 }]
|
|
71
|
+
|
|
72
|
+
-- Invalid update: goes negative → commit fails
|
|
73
|
+
BEGIN;
|
|
74
|
+
UPDATE accounts SET balance = -10 WHERE id = 1;
|
|
75
|
+
COMMIT;
|
|
76
|
+
-- error: Integrity assertion failed: positive_balance
|
|
77
|
+
|
|
78
|
+
-- Verify state is unchanged (rolled back)
|
|
79
|
+
SELECT balance FROM accounts WHERE id = 1;
|
|
80
|
+
→ [{ "balance": 50 }]
|
|
81
|
+
|
|
82
|
+
DROP ASSERTION positive_balance;
|
|
83
|
+
DROP TABLE accounts;
|
|
84
|
+
-- run
|
|
85
|
+
|
|
86
|
+
-- ===================================
|
|
87
|
+
-- Multi-Table Co-Existence (FK-like)
|
|
88
|
+
-- ===================================
|
|
89
|
+
|
|
90
|
+
CREATE TABLE parents (id INTEGER PRIMARY KEY, name TEXT);
|
|
91
|
+
CREATE TABLE children (id INTEGER PRIMARY KEY, parent_id INTEGER);
|
|
92
|
+
INSERT INTO parents VALUES (1, 'Parent1');
|
|
93
|
+
INSERT INTO parents VALUES (2, 'Parent2');
|
|
94
|
+
-- run
|
|
95
|
+
|
|
96
|
+
-- Every child must reference an existing parent
|
|
97
|
+
CREATE ASSERTION fk_children CHECK (NOT EXISTS (
|
|
98
|
+
SELECT 1 FROM children c WHERE NOT EXISTS (
|
|
99
|
+
SELECT 1 FROM parents p WHERE p.id = c.parent_id
|
|
100
|
+
)
|
|
101
|
+
));
|
|
102
|
+
-- run
|
|
103
|
+
|
|
104
|
+
-- Valid insert: parent exists
|
|
105
|
+
BEGIN;
|
|
106
|
+
INSERT INTO children VALUES (10, 1);
|
|
107
|
+
COMMIT;
|
|
108
|
+
-- run
|
|
109
|
+
|
|
110
|
+
SELECT id FROM children;
|
|
111
|
+
→ [{ "id": 10 }]
|
|
112
|
+
|
|
113
|
+
-- Invalid insert: parent doesn't exist → commit fails
|
|
114
|
+
BEGIN;
|
|
115
|
+
INSERT INTO children VALUES (20, 999);
|
|
116
|
+
COMMIT;
|
|
117
|
+
-- error: Integrity assertion failed: fk_children
|
|
118
|
+
|
|
119
|
+
-- Verify nothing was committed
|
|
120
|
+
SELECT COUNT(*) FROM children WHERE id = 20;
|
|
121
|
+
→ [{ "count(*)": 0 }]
|
|
122
|
+
|
|
123
|
+
DROP ASSERTION fk_children;
|
|
124
|
+
DROP TABLE children;
|
|
125
|
+
DROP TABLE parents;
|
|
126
|
+
-- run
|
|
127
|
+
|
|
128
|
+
-- ===================================
|
|
129
|
+
-- Aggregate-Based Global Assertion
|
|
130
|
+
-- ===================================
|
|
131
|
+
|
|
132
|
+
CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT);
|
|
133
|
+
CREATE ASSERTION max_items CHECK ((SELECT COUNT(*) FROM items) <= 3);
|
|
134
|
+
-- run
|
|
135
|
+
|
|
136
|
+
INSERT INTO items VALUES (1, 'a');
|
|
137
|
+
INSERT INTO items VALUES (2, 'b');
|
|
138
|
+
INSERT INTO items VALUES (3, 'c');
|
|
139
|
+
SELECT COUNT(*) FROM items;
|
|
140
|
+
→ [{ "count(*)": 3 }]
|
|
141
|
+
|
|
142
|
+
-- Fourth insert should fail at commit
|
|
143
|
+
BEGIN;
|
|
144
|
+
INSERT INTO items VALUES (4, 'd');
|
|
145
|
+
COMMIT;
|
|
146
|
+
-- error: Integrity assertion failed: max_items
|
|
147
|
+
|
|
148
|
+
-- Verify only 3 items
|
|
149
|
+
SELECT COUNT(*) FROM items;
|
|
150
|
+
→ [{ "count(*)": 3 }]
|
|
151
|
+
|
|
152
|
+
DROP ASSERTION max_items;
|
|
153
|
+
DROP TABLE items;
|
|
154
|
+
-- run
|
|
155
|
+
|
|
156
|
+
-- ===================================
|
|
157
|
+
-- Rollback Clears Violations
|
|
158
|
+
-- ===================================
|
|
159
|
+
|
|
160
|
+
CREATE TABLE vals (id INTEGER PRIMARY KEY, v INTEGER);
|
|
161
|
+
CREATE ASSERTION vals_positive CHECK (NOT EXISTS (SELECT 1 FROM vals WHERE v < 0));
|
|
162
|
+
-- run
|
|
163
|
+
|
|
164
|
+
-- Insert negative value, then rollback before commit
|
|
165
|
+
BEGIN;
|
|
166
|
+
INSERT INTO vals VALUES (1, -5);
|
|
167
|
+
ROLLBACK;
|
|
168
|
+
-- run
|
|
169
|
+
|
|
170
|
+
-- Data should be clean
|
|
171
|
+
SELECT COUNT(*) FROM vals;
|
|
172
|
+
→ [{ "count(*)": 0 }]
|
|
173
|
+
|
|
174
|
+
-- Valid insert works fine
|
|
175
|
+
INSERT INTO vals VALUES (1, 10);
|
|
176
|
+
COMMIT;
|
|
177
|
+
SELECT v FROM vals WHERE id = 1;
|
|
178
|
+
→ [{ "v": 10 }]
|
|
179
|
+
|
|
180
|
+
DROP ASSERTION vals_positive;
|
|
181
|
+
DROP TABLE vals;
|
|
182
|
+
-- run
|
|
183
|
+
|
|
184
|
+
-- ===================================
|
|
185
|
+
-- Savepoint Interaction
|
|
186
|
+
-- ===================================
|
|
187
|
+
|
|
188
|
+
CREATE TABLE sp_data (id INTEGER PRIMARY KEY, val INTEGER);
|
|
189
|
+
CREATE ASSERTION sp_positive CHECK (NOT EXISTS (SELECT 1 FROM sp_data WHERE val < 0));
|
|
190
|
+
-- run
|
|
191
|
+
|
|
192
|
+
-- Insert valid data, then invalid inside savepoint, rollback savepoint, then commit
|
|
193
|
+
BEGIN;
|
|
194
|
+
INSERT INTO sp_data VALUES (1, 10);
|
|
195
|
+
SAVEPOINT sp1;
|
|
196
|
+
INSERT INTO sp_data VALUES (2, -5);
|
|
197
|
+
ROLLBACK TO SAVEPOINT sp1;
|
|
198
|
+
COMMIT;
|
|
199
|
+
-- run
|
|
200
|
+
|
|
201
|
+
SELECT val FROM sp_data;
|
|
202
|
+
→ [{ "val": 10 }]
|
|
203
|
+
|
|
204
|
+
DROP ASSERTION sp_positive;
|
|
205
|
+
DROP TABLE sp_data;
|
|
206
|
+
-- run
|
|
207
|
+
|
|
208
|
+
-- ===================================
|
|
209
|
+
-- DROP ASSERTION IF EXISTS
|
|
210
|
+
-- ===================================
|
|
211
|
+
|
|
212
|
+
-- Dropping non-existent assertion without IF EXISTS → error
|
|
213
|
+
DROP ASSERTION nonexistent;
|
|
214
|
+
-- error: Assertion nonexistent not found
|
|
215
|
+
|
|
216
|
+
-- Dropping non-existent assertion with IF EXISTS → no error
|
|
217
|
+
DROP ASSERTION IF EXISTS nonexistent;
|
|
218
|
+
-- run
|
|
219
|
+
|
|
220
|
+
-- ===================================
|
|
221
|
+
-- Duplicate CREATE ASSERTION → Error
|
|
222
|
+
-- ===================================
|
|
223
|
+
|
|
224
|
+
CREATE TABLE dummy (id INTEGER PRIMARY KEY);
|
|
225
|
+
-- run
|
|
226
|
+
|
|
227
|
+
CREATE ASSERTION dup_test CHECK (1 = 1);
|
|
228
|
+
-- run
|
|
229
|
+
|
|
230
|
+
CREATE ASSERTION dup_test CHECK (1 = 1);
|
|
231
|
+
-- error: Assertion dup_test already exists
|
|
232
|
+
|
|
233
|
+
DROP ASSERTION dup_test;
|
|
234
|
+
DROP TABLE dummy;
|
|
235
|
+
-- run
|
|
236
|
+
|
|
237
|
+
-- ===================================
|
|
238
|
+
-- Autocommit Mode: Assertion Enforced
|
|
239
|
+
-- ===================================
|
|
240
|
+
|
|
241
|
+
CREATE TABLE auto_data (id INTEGER PRIMARY KEY, val INTEGER);
|
|
242
|
+
CREATE ASSERTION auto_positive CHECK (NOT EXISTS (SELECT 1 FROM auto_data WHERE val < 0));
|
|
243
|
+
-- run
|
|
244
|
+
|
|
245
|
+
-- In autocommit mode, each DML is implicitly committed.
|
|
246
|
+
-- This should fail because assertion is checked at implicit commit.
|
|
247
|
+
INSERT INTO auto_data VALUES (1, -10);
|
|
248
|
+
-- error: Integrity assertion failed: auto_positive
|
|
249
|
+
|
|
250
|
+
-- Verify nothing was inserted
|
|
251
|
+
SELECT COUNT(*) FROM auto_data;
|
|
252
|
+
→ [{ "count(*)": 0 }]
|
|
253
|
+
|
|
254
|
+
-- Valid insert should work
|
|
255
|
+
INSERT INTO auto_data VALUES (1, 10);
|
|
256
|
+
SELECT val FROM auto_data WHERE id = 1;
|
|
257
|
+
→ [{ "val": 10 }]
|
|
258
|
+
|
|
259
|
+
DROP ASSERTION auto_positive;
|
|
260
|
+
DROP TABLE auto_data;
|
|
261
|
+
-- run
|
|
262
|
+
|
|
263
|
+
-- ===================================
|
|
264
|
+
-- Assertion Not Impacted by Unrelated Table
|
|
265
|
+
-- ===================================
|
|
266
|
+
|
|
267
|
+
CREATE TABLE watched (id INTEGER PRIMARY KEY, val INTEGER);
|
|
268
|
+
CREATE TABLE unrelated (id INTEGER PRIMARY KEY, val INTEGER);
|
|
269
|
+
CREATE ASSERTION watched_check CHECK (NOT EXISTS (SELECT 1 FROM watched WHERE val < 0));
|
|
270
|
+
-- run
|
|
271
|
+
|
|
272
|
+
-- Insert into unrelated table should not trigger watched_check
|
|
273
|
+
INSERT INTO unrelated VALUES (1, -100);
|
|
274
|
+
COMMIT;
|
|
275
|
+
SELECT val FROM unrelated WHERE id = 1;
|
|
276
|
+
→ [{ "val": -100 }]
|
|
277
|
+
|
|
278
|
+
DROP ASSERTION watched_check;
|
|
279
|
+
DROP TABLE unrelated;
|
|
280
|
+
DROP TABLE watched;
|
|
281
|
+
-- run
|
|
282
|
+
|
|
283
|
+
-- ===================================
|
|
284
|
+
-- Explain Assertion Diagnostics
|
|
285
|
+
-- ===================================
|
|
286
|
+
|
|
287
|
+
CREATE TABLE t1 (id INTEGER PRIMARY KEY, u TEXT UNIQUE);
|
|
288
|
+
CREATE TABLE t2 (id INTEGER PRIMARY KEY, t1_id INTEGER, FOREIGN KEY (t1_id) REFERENCES t1(id));
|
|
289
|
+
-- run
|
|
290
|
+
|
|
291
|
+
-- Global-style assertion (aggregate): should classify t2 as global
|
|
292
|
+
CREATE ASSERTION a_global CHECK ((SELECT COUNT(*) FROM t2) = (SELECT COUNT(*) FROM t2));
|
|
293
|
+
-- run
|
|
294
|
+
|
|
295
|
+
SELECT EXISTS(SELECT 1 FROM explain_assertion('a_global') WHERE classification = 'global') AS ok;
|
|
296
|
+
→ [{ "ok": true }]
|
|
297
|
+
|
|
298
|
+
DROP ASSERTION a_global;
|
|
299
|
+
-- run
|
|
300
|
+
|
|
301
|
+
-- Row-specific-style assertion: equality on PK reduces to row-specific and exposes pk params
|
|
302
|
+
CREATE ASSERTION a_row CHECK (EXISTS (SELECT 1 FROM t1 WHERE id = 1));
|
|
303
|
+
-- run
|
|
304
|
+
|
|
305
|
+
SELECT COALESCE(prepared_pk_params, '[]') AS prepared_pk_params FROM explain_assertion('a_row') WHERE classification = 'row' LIMIT 1;
|
|
306
|
+
→ [{ "prepared_pk_params": "[\"pk0\"]" }]
|
|
307
|
+
|
|
308
|
+
DROP ASSERTION a_row;
|
|
309
|
+
DROP TABLE t2;
|
|
310
|
+
DROP TABLE t1;
|
|
311
|
+
-- run
|
|
312
|
+
|
|
313
|
+
-- ===================================
|
|
314
|
+
-- Multiple Assertions
|
|
315
|
+
-- ===================================
|
|
316
|
+
|
|
317
|
+
CREATE TABLE multi (id INTEGER PRIMARY KEY, a INTEGER, b INTEGER);
|
|
318
|
+
CREATE ASSERTION multi_a_pos CHECK (NOT EXISTS (SELECT 1 FROM multi WHERE a < 0));
|
|
319
|
+
CREATE ASSERTION multi_b_pos CHECK (NOT EXISTS (SELECT 1 FROM multi WHERE b < 0));
|
|
320
|
+
-- run
|
|
321
|
+
|
|
322
|
+
-- Both valid
|
|
323
|
+
INSERT INTO multi VALUES (1, 10, 20);
|
|
324
|
+
COMMIT;
|
|
325
|
+
SELECT COUNT(*) FROM multi;
|
|
326
|
+
→ [{ "count(*)": 1 }]
|
|
327
|
+
|
|
328
|
+
-- a valid, b invalid → fails multi_b_pos
|
|
329
|
+
BEGIN;
|
|
330
|
+
INSERT INTO multi VALUES (2, 5, -1);
|
|
331
|
+
COMMIT;
|
|
332
|
+
-- error: Integrity assertion failed: multi_b_pos
|
|
333
|
+
|
|
334
|
+
-- a invalid, b valid → fails multi_a_pos
|
|
335
|
+
BEGIN;
|
|
336
|
+
INSERT INTO multi VALUES (3, -1, 5);
|
|
337
|
+
COMMIT;
|
|
338
|
+
-- error: Integrity assertion failed: multi_a_pos
|
|
339
|
+
|
|
340
|
+
-- Verify only first row exists
|
|
341
|
+
SELECT COUNT(*) FROM multi;
|
|
342
|
+
→ [{ "count(*)": 1 }]
|
|
343
|
+
|
|
344
|
+
DROP ASSERTION multi_a_pos;
|
|
345
|
+
DROP ASSERTION multi_b_pos;
|
|
346
|
+
DROP TABLE multi;
|
|
347
|
+
-- run
|
|
348
|
+
|
|
349
|
+
-- ===================================
|
|
350
|
+
-- Group-Classified Assertion: Parameterized Delta Execution
|
|
351
|
+
-- ===================================
|
|
352
|
+
-- A subquery that aggregates by PK should be classified as 'group' so the
|
|
353
|
+
-- delta executor parameterizes per changed group key instead of running the
|
|
354
|
+
-- full violation query globally.
|
|
355
|
+
|
|
356
|
+
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, qty INTEGER);
|
|
357
|
+
-- run
|
|
358
|
+
|
|
359
|
+
-- The inner aggregate (GROUP BY id) is row-unique per group key. The outer
|
|
360
|
+
-- predicate asserts every per-id total is non-negative.
|
|
361
|
+
CREATE ASSERTION orders_nonneg CHECK (NOT EXISTS (
|
|
362
|
+
SELECT 1 FROM (SELECT id, SUM(qty) AS s FROM orders GROUP BY id) WHERE s < 0
|
|
363
|
+
));
|
|
364
|
+
-- run
|
|
365
|
+
|
|
366
|
+
-- Valid inserts: pass the assertion.
|
|
367
|
+
INSERT INTO orders VALUES (1, 10, 5);
|
|
368
|
+
INSERT INTO orders VALUES (2, 11, 7);
|
|
369
|
+
INSERT INTO orders VALUES (3, 10, 3);
|
|
370
|
+
COMMIT;
|
|
371
|
+
SELECT COUNT(*) FROM orders;
|
|
372
|
+
→ [{ "count(*)": 3 }]
|
|
373
|
+
|
|
374
|
+
-- Invalid insert at commit: qty negative trips the per-group check.
|
|
375
|
+
BEGIN;
|
|
376
|
+
INSERT INTO orders VALUES (4, 10, -100);
|
|
377
|
+
COMMIT;
|
|
378
|
+
-- error: Integrity assertion failed: orders_nonneg
|
|
379
|
+
|
|
380
|
+
-- Verify rollback.
|
|
381
|
+
SELECT COUNT(*) FROM orders;
|
|
382
|
+
→ [{ "count(*)": 3 }]
|
|
383
|
+
|
|
384
|
+
DROP ASSERTION orders_nonneg;
|
|
385
|
+
DROP TABLE orders;
|
|
386
|
+
-- run
|
|
387
|
+
|
|
388
|
+
-- ===================================
|
|
389
|
+
-- Group Assertion: NULL Group Key (repro for delta-null-group-key)
|
|
390
|
+
-- ===================================
|
|
391
|
+
-- The per-group residual binds the changed group key into the inner scan.
|
|
392
|
+
-- When the group-key column is NULL, the residual's `col = :gk` would
|
|
393
|
+
-- evaluate UNKNOWN under naive equality and silently skip the NULL group.
|
|
394
|
+
-- These cases verify that NULL groups are re-evaluated correctly.
|
|
395
|
+
|
|
396
|
+
CREATE TABLE orders_nullable (id INTEGER PRIMARY KEY, customer_id INTEGER NULL, qty INTEGER);
|
|
397
|
+
CREATE ASSERTION onn_nonneg CHECK (NOT EXISTS (
|
|
398
|
+
SELECT 1 FROM (SELECT customer_id, SUM(qty) AS s FROM orders_nullable GROUP BY customer_id) WHERE s < 0
|
|
399
|
+
));
|
|
400
|
+
-- run
|
|
401
|
+
|
|
402
|
+
-- Seed: NULL group has positive sum, commits cleanly.
|
|
403
|
+
INSERT INTO orders_nullable VALUES (1, NULL, 5);
|
|
404
|
+
COMMIT;
|
|
405
|
+
SELECT COUNT(*) FROM orders_nullable;
|
|
406
|
+
→ [{ "count(*)": 1 }]
|
|
407
|
+
|
|
408
|
+
-- Violation in the NULL group: per-group residual must re-evaluate the NULL
|
|
409
|
+
-- group and flag the negative sum.
|
|
410
|
+
BEGIN;
|
|
411
|
+
INSERT INTO orders_nullable VALUES (2, NULL, -100);
|
|
412
|
+
COMMIT;
|
|
413
|
+
-- error: Integrity assertion failed: onn_nonneg
|
|
414
|
+
|
|
415
|
+
-- Verify rollback.
|
|
416
|
+
SELECT COUNT(*) FROM orders_nullable;
|
|
417
|
+
→ [{ "count(*)": 1 }]
|
|
418
|
+
|
|
419
|
+
DROP ASSERTION onn_nonneg;
|
|
420
|
+
DROP TABLE orders_nullable;
|
|
421
|
+
-- run
|
|
422
|
+
|
|
423
|
+
-- ===================================
|
|
424
|
+
-- Group Assertion: Cross-Group Isolation with NULL Group Present
|
|
425
|
+
-- ===================================
|
|
426
|
+
-- A passing NULL group must not mask a violation in a separate non-NULL
|
|
427
|
+
-- group, and a passing non-NULL group must not mask a NULL-group violation.
|
|
428
|
+
|
|
429
|
+
CREATE TABLE orders_iso (id INTEGER PRIMARY KEY, customer_id INTEGER NULL, qty INTEGER);
|
|
430
|
+
CREATE ASSERTION oiso_nonneg CHECK (NOT EXISTS (
|
|
431
|
+
SELECT 1 FROM (SELECT customer_id, SUM(qty) AS s FROM orders_iso GROUP BY customer_id) WHERE s < 0
|
|
432
|
+
));
|
|
433
|
+
-- run
|
|
434
|
+
|
|
435
|
+
INSERT INTO orders_iso VALUES (1, NULL, 5);
|
|
436
|
+
INSERT INTO orders_iso VALUES (2, 100, 10);
|
|
437
|
+
COMMIT;
|
|
438
|
+
SELECT COUNT(*) FROM orders_iso;
|
|
439
|
+
→ [{ "count(*)": 2 }]
|
|
440
|
+
|
|
441
|
+
-- Violation in non-NULL group while NULL group still positive.
|
|
442
|
+
BEGIN;
|
|
443
|
+
INSERT INTO orders_iso VALUES (3, 100, -50);
|
|
444
|
+
COMMIT;
|
|
445
|
+
-- error: Integrity assertion failed: oiso_nonneg
|
|
446
|
+
|
|
447
|
+
SELECT COUNT(*) FROM orders_iso;
|
|
448
|
+
→ [{ "count(*)": 2 }]
|
|
449
|
+
|
|
450
|
+
-- Violation in NULL group while non-NULL group still positive.
|
|
451
|
+
BEGIN;
|
|
452
|
+
INSERT INTO orders_iso VALUES (4, NULL, -50);
|
|
453
|
+
COMMIT;
|
|
454
|
+
-- error: Integrity assertion failed: oiso_nonneg
|
|
455
|
+
|
|
456
|
+
SELECT COUNT(*) FROM orders_iso;
|
|
457
|
+
→ [{ "count(*)": 2 }]
|
|
458
|
+
|
|
459
|
+
DROP ASSERTION oiso_nonneg;
|
|
460
|
+
DROP TABLE orders_iso;
|
|
461
|
+
-- run
|
|
462
|
+
|
|
463
|
+
-- ===================================
|
|
464
|
+
-- Group Assertion: UPDATE Moves Row Across NULL/non-NULL Groups
|
|
465
|
+
-- ===================================
|
|
466
|
+
-- UPDATE projects both OLD and NEW tuples; when one side has NULL in the
|
|
467
|
+
-- group key, the residual must still re-check that group.
|
|
468
|
+
|
|
469
|
+
CREATE TABLE orders_mv (id INTEGER PRIMARY KEY, customer_id INTEGER NULL, qty INTEGER);
|
|
470
|
+
CREATE ASSERTION omv_nonneg CHECK (NOT EXISTS (
|
|
471
|
+
SELECT 1 FROM (SELECT customer_id, SUM(qty) AS s FROM orders_mv GROUP BY customer_id) WHERE s < 0
|
|
472
|
+
));
|
|
473
|
+
-- run
|
|
474
|
+
|
|
475
|
+
-- Seed: NULL group sum=150, group 100 sum=50.
|
|
476
|
+
INSERT INTO orders_mv VALUES (1, NULL, 50);
|
|
477
|
+
INSERT INTO orders_mv VALUES (2, NULL, 100);
|
|
478
|
+
INSERT INTO orders_mv VALUES (3, 100, 50);
|
|
479
|
+
COMMIT;
|
|
480
|
+
|
|
481
|
+
-- Move row 1 from NULL group to group 100, switching qty to a value that
|
|
482
|
+
-- drives group 100 negative. NULL group is unaffected (100, ok); group 100
|
|
483
|
+
-- becomes 50 + (-200) = -150 and must trip the assertion.
|
|
484
|
+
BEGIN;
|
|
485
|
+
UPDATE orders_mv SET customer_id = 100, qty = -200 WHERE id = 1;
|
|
486
|
+
COMMIT;
|
|
487
|
+
-- error: Integrity assertion failed: omv_nonneg
|
|
488
|
+
|
|
489
|
+
-- Verify rollback.
|
|
490
|
+
SELECT customer_id, qty FROM orders_mv WHERE id = 1;
|
|
491
|
+
→ [{ "customer_id": null, "qty": 50 }]
|
|
492
|
+
|
|
493
|
+
-- Move row 3 from group 100 into the NULL group with a value that drives
|
|
494
|
+
-- the NULL group negative. Group 100 becomes empty/positive; NULL group
|
|
495
|
+
-- becomes 50 + 100 + (-500) = -350 and must trip the assertion.
|
|
496
|
+
BEGIN;
|
|
497
|
+
UPDATE orders_mv SET customer_id = NULL, qty = -500 WHERE id = 3;
|
|
498
|
+
COMMIT;
|
|
499
|
+
-- error: Integrity assertion failed: omv_nonneg
|
|
500
|
+
|
|
501
|
+
SELECT customer_id, qty FROM orders_mv WHERE id = 3;
|
|
502
|
+
→ [{ "customer_id": 100, "qty": 50 }]
|
|
503
|
+
|
|
504
|
+
DROP ASSERTION omv_nonneg;
|
|
505
|
+
DROP TABLE orders_mv;
|
|
506
|
+
-- run
|
|
507
|
+
|
|
508
|
+
-- ===================================
|
|
509
|
+
-- Group Assertion: Multi-Column Group Key with Mixed NULLs
|
|
510
|
+
-- ===================================
|
|
511
|
+
-- Per-conjunct NULL handling must compose: GROUP BY a, b where one of
|
|
512
|
+
-- a/b is NULL on some rows.
|
|
513
|
+
|
|
514
|
+
CREATE TABLE orders_mc (id INTEGER PRIMARY KEY, a INTEGER NULL, b INTEGER NULL, qty INTEGER);
|
|
515
|
+
CREATE ASSERTION omc_nonneg CHECK (NOT EXISTS (
|
|
516
|
+
SELECT 1 FROM (SELECT a, b, SUM(qty) AS s FROM orders_mc GROUP BY a, b) WHERE s < 0
|
|
517
|
+
));
|
|
518
|
+
-- run
|
|
519
|
+
|
|
520
|
+
-- Seed: groups (NULL,1)=15, (1,NULL)=10, (1,1)=20.
|
|
521
|
+
INSERT INTO orders_mc VALUES (1, NULL, 1, 10);
|
|
522
|
+
INSERT INTO orders_mc VALUES (2, NULL, 1, 5);
|
|
523
|
+
INSERT INTO orders_mc VALUES (3, 1, NULL, 10);
|
|
524
|
+
INSERT INTO orders_mc VALUES (4, 1, 1, 20);
|
|
525
|
+
COMMIT;
|
|
526
|
+
|
|
527
|
+
-- Violation in (NULL, 1) group.
|
|
528
|
+
BEGIN;
|
|
529
|
+
INSERT INTO orders_mc VALUES (5, NULL, 1, -100);
|
|
530
|
+
COMMIT;
|
|
531
|
+
-- error: Integrity assertion failed: omc_nonneg
|
|
532
|
+
|
|
533
|
+
-- Violation in (1, NULL) group.
|
|
534
|
+
BEGIN;
|
|
535
|
+
INSERT INTO orders_mc VALUES (6, 1, NULL, -100);
|
|
536
|
+
COMMIT;
|
|
537
|
+
-- error: Integrity assertion failed: omc_nonneg
|
|
538
|
+
|
|
539
|
+
-- Passing insert into (NULL, NULL) group does not flag the existing
|
|
540
|
+
-- non-violating mixed-NULL groups.
|
|
541
|
+
INSERT INTO orders_mc VALUES (7, NULL, NULL, 1);
|
|
542
|
+
COMMIT;
|
|
543
|
+
SELECT COUNT(*) FROM orders_mc;
|
|
544
|
+
→ [{ "count(*)": 5 }]
|
|
545
|
+
|
|
546
|
+
DROP ASSERTION omc_nonneg;
|
|
547
|
+
DROP TABLE orders_mc;
|
|
548
|
+
-- run
|
|
549
|
+
|
|
550
|
+
-- ===================================
|
|
551
|
+
-- Row Assertion: Nullable UNIQUE Column Present (repro for delta-null-row-key)
|
|
552
|
+
-- ===================================
|
|
553
|
+
-- A row-classified assertion's residual is `col = :pk_i` per chosen key column.
|
|
554
|
+
-- When `chooseRowKey` lands on a nullable UNIQUE column instead of the PK, a
|
|
555
|
+
-- change tuple with NULL in that column would silently bypass the residual
|
|
556
|
+
-- under naive equality. In practice the optimizer's FD/EC closure propagates
|
|
557
|
+
-- the UNIQUE equality to PK so the binding is PK; this block guards against
|
|
558
|
+
-- regressions in (a) the binding picker preferring PK and (b) the residual
|
|
559
|
+
-- builder handling nullable key columns NULL-safely if the picker ever lands
|
|
560
|
+
-- on one. Pattern after `onn_nonneg`.
|
|
561
|
+
|
|
562
|
+
CREATE TABLE accounts (id INTEGER PRIMARY KEY, ext_id TEXT NULL UNIQUE, balance INTEGER);
|
|
563
|
+
CREATE ASSERTION rnn_balance CHECK (NOT EXISTS (
|
|
564
|
+
SELECT 1 FROM accounts WHERE id = 1 AND balance < 0
|
|
565
|
+
));
|
|
566
|
+
-- run
|
|
567
|
+
|
|
568
|
+
-- Verify the binding lands on PK via FD closure (regression guard for
|
|
569
|
+
-- chooseRowKey's PK-preferred behaviour).
|
|
570
|
+
SELECT COALESCE(prepared_pk_params, '[]') AS prepared_pk_params
|
|
571
|
+
FROM explain_assertion('rnn_balance') WHERE classification = 'row' LIMIT 1;
|
|
572
|
+
→ [{ "prepared_pk_params": "[\"pk0\"]" }]
|
|
573
|
+
|
|
574
|
+
-- Seed a row whose nullable UNIQUE column is NULL. Per SQL standard NULLs are
|
|
575
|
+
-- distinct, so this commits cleanly.
|
|
576
|
+
INSERT INTO accounts VALUES (1, NULL, 100);
|
|
577
|
+
COMMIT;
|
|
578
|
+
SELECT COUNT(*) FROM accounts;
|
|
579
|
+
→ [{ "count(*)": 1 }]
|
|
580
|
+
|
|
581
|
+
-- Update the NULL-keyed row to violate the assertion: the row residual must
|
|
582
|
+
-- re-evaluate this tuple even though its UNIQUE column is NULL. The PK
|
|
583
|
+
-- binding makes this trivial today, but the per-column-nullable residual
|
|
584
|
+
-- shape keeps it correct if `chooseRowKey` ever lands on `ext_id`.
|
|
585
|
+
BEGIN;
|
|
586
|
+
UPDATE accounts SET balance = -50 WHERE id = 1;
|
|
587
|
+
COMMIT;
|
|
588
|
+
-- error: Integrity assertion failed: rnn_balance
|
|
589
|
+
|
|
590
|
+
SELECT balance FROM accounts WHERE id = 1;
|
|
591
|
+
→ [{ "balance": 100 }]
|
|
592
|
+
|
|
593
|
+
DROP ASSERTION rnn_balance;
|
|
594
|
+
DROP TABLE accounts;
|
|
595
|
+
-- run
|
|
596
|
+
|
|
597
|
+
-- ===================================
|
|
598
|
+
-- ALTER TABLE ... RENAME propagates into assertion bodies
|
|
599
|
+
-- ===================================
|
|
600
|
+
-- A rename rewrites the stored CHECK expression (and the derived violation SQL
|
|
601
|
+
-- the commit-time evaluator re-parses) the same way it rewrites a view body.
|
|
602
|
+
-- Before this propagation existed, every write to the renamed table failed with
|
|
603
|
+
-- "Table 't' not found" / "Column not found: x". Catalog-level invariants
|
|
604
|
+
-- (event fired, dependentTables re-keyed, home-schema scoping) are covered in
|
|
605
|
+
-- test/assertion-rename-propagation.spec.ts.
|
|
606
|
+
|
|
607
|
+
-- --- TABLE rename, with a table-qualified self-reference in the body ---
|
|
608
|
+
|
|
609
|
+
CREATE TABLE ren_t (x INTEGER PRIMARY KEY);
|
|
610
|
+
CREATE ASSERTION ren_t_nonneg CHECK (NOT EXISTS (
|
|
611
|
+
SELECT 1 FROM ren_t WHERE ren_t.x < 0
|
|
612
|
+
));
|
|
613
|
+
INSERT INTO ren_t VALUES (1);
|
|
614
|
+
COMMIT;
|
|
615
|
+
ALTER TABLE ren_t RENAME TO ren_t2;
|
|
616
|
+
-- run
|
|
617
|
+
|
|
618
|
+
-- The stored violation SQL names the new table in both the FROM and the
|
|
619
|
+
-- qualified column reference.
|
|
620
|
+
SELECT violation_sql FROM assertion_info() WHERE name = 'ren_t_nonneg';
|
|
621
|
+
→ [{ "violation_sql": "select 1 where not (not exists (select 1 from ren_t2 where ren_t2.x < 0))" }]
|
|
622
|
+
|
|
623
|
+
-- A benign write commits (pre-fix this failed with "Table 'ren_t' not found").
|
|
624
|
+
INSERT INTO ren_t2 VALUES (7);
|
|
625
|
+
COMMIT;
|
|
626
|
+
SELECT COUNT(*) FROM ren_t2;
|
|
627
|
+
→ [{ "count(*)": 2 }]
|
|
628
|
+
|
|
629
|
+
-- And the rule still enforces against the renamed table.
|
|
630
|
+
BEGIN;
|
|
631
|
+
INSERT INTO ren_t2 VALUES (-1);
|
|
632
|
+
COMMIT;
|
|
633
|
+
-- error: Integrity assertion failed: ren_t_nonneg
|
|
634
|
+
|
|
635
|
+
SELECT COUNT(*) FROM ren_t2;
|
|
636
|
+
→ [{ "count(*)": 2 }]
|
|
637
|
+
|
|
638
|
+
DROP ASSERTION ren_t_nonneg;
|
|
639
|
+
DROP TABLE ren_t2;
|
|
640
|
+
-- run
|
|
641
|
+
|
|
642
|
+
-- --- COLUMN rename ---
|
|
643
|
+
|
|
644
|
+
CREATE TABLE ren_u (id INTEGER PRIMARY KEY, x INTEGER);
|
|
645
|
+
CREATE ASSERTION ren_u_nonneg CHECK (NOT EXISTS (
|
|
646
|
+
SELECT 1 FROM ren_u WHERE x < 0
|
|
647
|
+
));
|
|
648
|
+
INSERT INTO ren_u VALUES (1, 10);
|
|
649
|
+
COMMIT;
|
|
650
|
+
ALTER TABLE ren_u RENAME COLUMN x TO y;
|
|
651
|
+
-- run
|
|
652
|
+
|
|
653
|
+
SELECT violation_sql FROM assertion_info() WHERE name = 'ren_u_nonneg';
|
|
654
|
+
→ [{ "violation_sql": "select 1 where not (not exists (select 1 from ren_u where y < 0))" }]
|
|
655
|
+
|
|
656
|
+
-- Benign write commits (pre-fix: "Column not found: x").
|
|
657
|
+
INSERT INTO ren_u VALUES (2, 20);
|
|
658
|
+
COMMIT;
|
|
659
|
+
SELECT COUNT(*) FROM ren_u;
|
|
660
|
+
→ [{ "count(*)": 2 }]
|
|
661
|
+
|
|
662
|
+
BEGIN;
|
|
663
|
+
INSERT INTO ren_u VALUES (3, -3);
|
|
664
|
+
COMMIT;
|
|
665
|
+
-- error: Integrity assertion failed: ren_u_nonneg
|
|
666
|
+
|
|
667
|
+
SELECT COUNT(*) FROM ren_u;
|
|
668
|
+
→ [{ "count(*)": 2 }]
|
|
669
|
+
|
|
670
|
+
DROP ASSERTION ren_u_nonneg;
|
|
671
|
+
DROP TABLE ren_u;
|
|
672
|
+
-- run
|
|
673
|
+
|
|
674
|
+
-- --- Body shapes the walk has to follow ---
|
|
675
|
+
-- The rewriters are shared with the view-body path, so these shapes are covered
|
|
676
|
+
-- there too; pinned here because an assertion body is walked UNSEEDED (it owns
|
|
677
|
+
-- its own FROM scopes) and nothing else exercises that entry point.
|
|
678
|
+
|
|
679
|
+
-- Join: only the renamed side moves, in both the FROM and the ON clause.
|
|
680
|
+
CREATE TABLE ren_p (id INTEGER PRIMARY KEY);
|
|
681
|
+
CREATE TABLE ren_c (id INTEGER PRIMARY KEY, pid INTEGER);
|
|
682
|
+
CREATE ASSERTION ren_join CHECK (NOT EXISTS (
|
|
683
|
+
SELECT 1 FROM ren_c JOIN ren_p ON ren_c.pid = ren_p.id WHERE ren_c.id < 0
|
|
684
|
+
));
|
|
685
|
+
ALTER TABLE ren_p RENAME TO ren_p2;
|
|
686
|
+
-- run
|
|
687
|
+
|
|
688
|
+
SELECT violation_sql FROM assertion_info() WHERE name = 'ren_join';
|
|
689
|
+
→ [{ "violation_sql": "select 1 where not (not exists (select 1 from ren_c inner join ren_p2 on ren_c.pid = ren_p2.id where ren_c.id < 0))" }]
|
|
690
|
+
|
|
691
|
+
INSERT INTO ren_p2 VALUES (1);
|
|
692
|
+
INSERT INTO ren_c VALUES (5, 1);
|
|
693
|
+
COMMIT;
|
|
694
|
+
BEGIN;
|
|
695
|
+
INSERT INTO ren_c VALUES (-1, 1);
|
|
696
|
+
COMMIT;
|
|
697
|
+
-- error: Integrity assertion failed: ren_join
|
|
698
|
+
|
|
699
|
+
DROP ASSERTION ren_join;
|
|
700
|
+
DROP TABLE ren_c;
|
|
701
|
+
DROP TABLE ren_p2;
|
|
702
|
+
-- run
|
|
703
|
+
|
|
704
|
+
-- Alias: the source name follows the rename, the alias does not.
|
|
705
|
+
CREATE TABLE ren_a (x INTEGER PRIMARY KEY);
|
|
706
|
+
CREATE ASSERTION ren_alias CHECK (NOT EXISTS (SELECT 1 FROM ren_a AS aa WHERE aa.x < 0));
|
|
707
|
+
ALTER TABLE ren_a RENAME TO ren_a2;
|
|
708
|
+
-- run
|
|
709
|
+
|
|
710
|
+
SELECT violation_sql FROM assertion_info() WHERE name = 'ren_alias';
|
|
711
|
+
→ [{ "violation_sql": "select 1 where not (not exists (select 1 from ren_a2 as aa where aa.x < 0))" }]
|
|
712
|
+
|
|
713
|
+
BEGIN;
|
|
714
|
+
INSERT INTO ren_a2 VALUES (-1);
|
|
715
|
+
COMMIT;
|
|
716
|
+
-- error: Integrity assertion failed: ren_alias
|
|
717
|
+
|
|
718
|
+
DROP ASSERTION ren_alias;
|
|
719
|
+
DROP TABLE ren_a2;
|
|
720
|
+
-- run
|
|
721
|
+
|
|
722
|
+
-- CTE: the rename reaches inside the WITH body; the CTE name is untouched.
|
|
723
|
+
CREATE TABLE ren_k (x INTEGER PRIMARY KEY);
|
|
724
|
+
CREATE ASSERTION ren_cte CHECK (NOT EXISTS (
|
|
725
|
+
WITH s AS (SELECT x FROM ren_k) SELECT 1 FROM s WHERE x < 0
|
|
726
|
+
));
|
|
727
|
+
ALTER TABLE ren_k RENAME TO ren_k2;
|
|
728
|
+
-- run
|
|
729
|
+
|
|
730
|
+
SELECT violation_sql FROM assertion_info() WHERE name = 'ren_cte';
|
|
731
|
+
→ [{ "violation_sql": "select 1 where not (not exists (with s as (select x from ren_k2) select 1 from s where x < 0))" }]
|
|
732
|
+
|
|
733
|
+
BEGIN;
|
|
734
|
+
INSERT INTO ren_k2 VALUES (-1);
|
|
735
|
+
COMMIT;
|
|
736
|
+
-- error: Integrity assertion failed: ren_cte
|
|
737
|
+
|
|
738
|
+
DROP ASSERTION ren_cte;
|
|
739
|
+
DROP TABLE ren_k2;
|
|
740
|
+
-- run
|
|
741
|
+
|
|
742
|
+
-- RENAME COLUMN reaches a table-qualified reference, not only a bare one.
|
|
743
|
+
CREATE TABLE ren_q (id INTEGER PRIMARY KEY, x INTEGER);
|
|
744
|
+
CREATE ASSERTION ren_qual CHECK (NOT EXISTS (SELECT 1 FROM ren_q WHERE ren_q.x < 0));
|
|
745
|
+
ALTER TABLE ren_q RENAME COLUMN x TO y;
|
|
746
|
+
-- run
|
|
747
|
+
|
|
748
|
+
SELECT violation_sql FROM assertion_info() WHERE name = 'ren_qual';
|
|
749
|
+
→ [{ "violation_sql": "select 1 where not (not exists (select 1 from ren_q where ren_q.y < 0))" }]
|
|
750
|
+
|
|
751
|
+
BEGIN;
|
|
752
|
+
INSERT INTO ren_q VALUES (1, -3);
|
|
753
|
+
COMMIT;
|
|
754
|
+
-- error: Integrity assertion failed: ren_qual
|
|
755
|
+
|
|
756
|
+
DROP ASSERTION ren_qual;
|
|
757
|
+
DROP TABLE ren_q;
|
|
758
|
+
-- run
|
|
759
|
+
|
|
760
|
+
-- --- Rename mid-transaction, with rows already written ---
|
|
761
|
+
-- The assertion evaluates at THIS commit, against the body the rename just
|
|
762
|
+
-- rewrote — the rewrite has to be visible to the same transaction that made it.
|
|
763
|
+
|
|
764
|
+
CREATE TABLE ren_x (x INTEGER PRIMARY KEY);
|
|
765
|
+
CREATE ASSERTION ren_x_nonneg CHECK (NOT EXISTS (SELECT 1 FROM ren_x WHERE x < 0));
|
|
766
|
+
COMMIT;
|
|
767
|
+
BEGIN;
|
|
768
|
+
INSERT INTO ren_x VALUES (3);
|
|
769
|
+
ALTER TABLE ren_x RENAME TO ren_x2;
|
|
770
|
+
INSERT INTO ren_x2 VALUES (4);
|
|
771
|
+
COMMIT;
|
|
772
|
+
SELECT COUNT(*) FROM ren_x2;
|
|
773
|
+
→ [{ "count(*)": 2 }]
|
|
774
|
+
|
|
775
|
+
BEGIN;
|
|
776
|
+
INSERT INTO ren_x2 VALUES (-9);
|
|
777
|
+
COMMIT;
|
|
778
|
+
-- error: Integrity assertion failed: ren_x_nonneg
|
|
779
|
+
|
|
780
|
+
DROP ASSERTION ren_x_nonneg;
|
|
781
|
+
DROP TABLE ren_x2;
|
|
782
|
+
-- run
|
|
783
|
+
|
|
784
|
+
-- --- A fresh table reusing the freed name is not governed by the assertion ---
|
|
785
|
+
-- The optimizer hoists an assertion's premises onto the tables its body names.
|
|
786
|
+
-- Pre-fix the body still said `ren_z`, so a new table created under the freed
|
|
787
|
+
-- name inherited a rule that was never about it — and rows it legitimately held
|
|
788
|
+
-- could be optimized away.
|
|
789
|
+
|
|
790
|
+
CREATE TABLE ren_z (x INTEGER PRIMARY KEY);
|
|
791
|
+
CREATE ASSERTION ren_z_nonneg CHECK (NOT EXISTS (SELECT 1 FROM ren_z WHERE x < 0));
|
|
792
|
+
ALTER TABLE ren_z RENAME TO ren_z2;
|
|
793
|
+
CREATE TABLE ren_z (x INTEGER PRIMARY KEY);
|
|
794
|
+
INSERT INTO ren_z VALUES (-5);
|
|
795
|
+
COMMIT;
|
|
796
|
+
|
|
797
|
+
-- The new ren_z keeps its negative row and stays visible to a matching filter.
|
|
798
|
+
SELECT COUNT(*) FROM ren_z WHERE x < 0;
|
|
799
|
+
→ [{ "count(*)": 1 }]
|
|
800
|
+
|
|
801
|
+
-- The rule still governs the renamed table.
|
|
802
|
+
BEGIN;
|
|
803
|
+
INSERT INTO ren_z2 VALUES (-1);
|
|
804
|
+
COMMIT;
|
|
805
|
+
-- error: Integrity assertion failed: ren_z_nonneg
|
|
806
|
+
|
|
807
|
+
DROP ASSERTION ren_z_nonneg;
|
|
808
|
+
DROP TABLE ren_z;
|
|
809
|
+
DROP TABLE ren_z2;
|
|
810
|
+
-- run
|
|
811
|
+
|
|
812
|
+
-- ===================================
|
|
813
|
+
-- Body validation at CREATE (arm 1)
|
|
814
|
+
-- ===================================
|
|
815
|
+
-- The body is planned when the assertion is created, at the same strictness a
|
|
816
|
+
-- view body is planned: missing table / missing column / unknown function all
|
|
817
|
+
-- fail the CREATE. Before this, each was accepted and then made every later
|
|
818
|
+
-- COMMIT in the whole database fail, naming the missing object but never the
|
|
819
|
+
-- assertion.
|
|
820
|
+
|
|
821
|
+
CREATE TABLE bv_t (x INTEGER PRIMARY KEY);
|
|
822
|
+
CREATE TABLE bv_other (i INTEGER PRIMARY KEY);
|
|
823
|
+
-- run
|
|
824
|
+
|
|
825
|
+
CREATE ASSERTION bv_missing_table CHECK (NOT EXISTS (SELECT 1 FROM bv_nope WHERE q < 0));
|
|
826
|
+
-- error: Cannot create assertion 'bv_missing_table': Table 'bv_nope' not found
|
|
827
|
+
|
|
828
|
+
CREATE ASSERTION bv_missing_column CHECK (NOT EXISTS (SELECT 1 FROM bv_t WHERE zzz < 0));
|
|
829
|
+
-- error: Cannot create assertion 'bv_missing_column': Column not found: zzz
|
|
830
|
+
|
|
831
|
+
CREATE ASSERTION bv_missing_fn CHECK (bv_nosuchfn(1) > 0);
|
|
832
|
+
-- error: Cannot create assertion 'bv_missing_fn': Function not found: bv_nosuchfn/1
|
|
833
|
+
|
|
834
|
+
-- None of the three were registered.
|
|
835
|
+
SELECT COUNT(*) AS n FROM assertion_info() WHERE name LIKE 'bv_%';
|
|
836
|
+
→ [{ "n": 0 }]
|
|
837
|
+
|
|
838
|
+
-- The database was never poisoned: an unrelated write still commits.
|
|
839
|
+
INSERT INTO bv_other VALUES (1);
|
|
840
|
+
COMMIT;
|
|
841
|
+
SELECT COUNT(*) AS n FROM bv_other;
|
|
842
|
+
→ [{ "n": 1 }]
|
|
843
|
+
|
|
844
|
+
DROP TABLE bv_t;
|
|
845
|
+
DROP TABLE bv_other;
|
|
846
|
+
-- run
|
|
847
|
+
|
|
848
|
+
-- ===================================
|
|
849
|
+
-- DROP refused while an assertion refers to the object (arm 2)
|
|
850
|
+
-- ===================================
|
|
851
|
+
|
|
852
|
+
-- --- DROP TABLE ---
|
|
853
|
+
|
|
854
|
+
CREATE TABLE dg_t (x INTEGER PRIMARY KEY);
|
|
855
|
+
CREATE TABLE dg_other (i INTEGER PRIMARY KEY);
|
|
856
|
+
CREATE ASSERTION dg_t_nonneg CHECK (NOT EXISTS (SELECT 1 FROM dg_t WHERE x < 0));
|
|
857
|
+
-- run
|
|
858
|
+
|
|
859
|
+
DROP TABLE dg_t;
|
|
860
|
+
-- error: cannot drop table 'main.dg_t': assertion 'dg_t_nonneg' still refers to it
|
|
861
|
+
|
|
862
|
+
-- IF EXISTS governs absence, not dependency — it does not weaken the guard.
|
|
863
|
+
DROP TABLE IF EXISTS dg_t;
|
|
864
|
+
-- error: cannot drop table 'main.dg_t': assertion 'dg_t_nonneg' still refers to it
|
|
865
|
+
|
|
866
|
+
-- The refusal is a clean no-op: the table is still there and still enforced.
|
|
867
|
+
INSERT INTO dg_t VALUES (1);
|
|
868
|
+
COMMIT;
|
|
869
|
+
SELECT COUNT(*) AS n FROM dg_t;
|
|
870
|
+
→ [{ "n": 1 }]
|
|
871
|
+
|
|
872
|
+
-- Dropping the rule first is the documented way out.
|
|
873
|
+
DROP ASSERTION dg_t_nonneg;
|
|
874
|
+
DROP TABLE dg_t;
|
|
875
|
+
INSERT INTO dg_other VALUES (1);
|
|
876
|
+
COMMIT;
|
|
877
|
+
SELECT COUNT(*) AS n FROM dg_other;
|
|
878
|
+
→ [{ "n": 1 }]
|
|
879
|
+
|
|
880
|
+
DROP TABLE dg_other;
|
|
881
|
+
-- run
|
|
882
|
+
|
|
883
|
+
-- --- DROP VIEW ---
|
|
884
|
+
|
|
885
|
+
CREATE TABLE dg_u (x INTEGER PRIMARY KEY);
|
|
886
|
+
CREATE VIEW dg_v AS SELECT * FROM dg_u;
|
|
887
|
+
CREATE ASSERTION dg_v_nonneg CHECK (NOT EXISTS (SELECT 1 FROM dg_v WHERE x < 0));
|
|
888
|
+
-- run
|
|
889
|
+
|
|
890
|
+
DROP VIEW dg_v;
|
|
891
|
+
-- error: cannot drop view 'main.dg_v': assertion 'dg_v_nonneg' still refers to it
|
|
892
|
+
|
|
893
|
+
DROP ASSERTION dg_v_nonneg;
|
|
894
|
+
DROP VIEW dg_v;
|
|
895
|
+
DROP TABLE dg_u;
|
|
896
|
+
-- run
|
|
897
|
+
|
|
898
|
+
-- --- DROP MATERIALIZED VIEW (and DROP TABLE on the same maintained table) ---
|
|
899
|
+
|
|
900
|
+
CREATE TABLE dg_w (x INTEGER PRIMARY KEY);
|
|
901
|
+
CREATE MATERIALIZED VIEW dg_mv AS SELECT x FROM dg_w;
|
|
902
|
+
CREATE ASSERTION dg_mv_nonneg CHECK (NOT EXISTS (SELECT 1 FROM dg_mv WHERE x < 0));
|
|
903
|
+
-- run
|
|
904
|
+
|
|
905
|
+
DROP MATERIALIZED VIEW dg_mv;
|
|
906
|
+
-- error: cannot drop materialized view 'main.dg_mv': assertion 'dg_mv_nonneg' still refers to it
|
|
907
|
+
|
|
908
|
+
-- DROP TABLE on a maintained table is guarded on the same seam.
|
|
909
|
+
DROP TABLE dg_mv;
|
|
910
|
+
-- error: cannot drop table 'main.dg_mv': assertion 'dg_mv_nonneg' still refers to it
|
|
911
|
+
|
|
912
|
+
DROP ASSERTION dg_mv_nonneg;
|
|
913
|
+
DROP MATERIALIZED VIEW dg_mv;
|
|
914
|
+
DROP TABLE dg_w;
|
|
915
|
+
-- run
|
|
916
|
+
|
|
917
|
+
-- --- Same-schema scoping ---
|
|
918
|
+
-- An assertion's unqualified table names resolve against its OWN schema first,
|
|
919
|
+
-- so an assertion living in `temp` must not be tripped by dropping a like-named
|
|
920
|
+
-- table in `main`.
|
|
921
|
+
|
|
922
|
+
CREATE TABLE main.dg_s (x INTEGER PRIMARY KEY);
|
|
923
|
+
CREATE TABLE temp.dg_s (x INTEGER PRIMARY KEY);
|
|
924
|
+
CREATE ASSERTION temp.dg_s_nonneg CHECK (NOT EXISTS (SELECT 1 FROM dg_s WHERE x < 0));
|
|
925
|
+
-- run
|
|
926
|
+
|
|
927
|
+
-- main.dg_s is not what the temp assertion names — `temp.dg_s` shadows it.
|
|
928
|
+
DROP TABLE main.dg_s;
|
|
929
|
+
-- run
|
|
930
|
+
|
|
931
|
+
DROP TABLE temp.dg_s;
|
|
932
|
+
-- error: cannot drop table 'temp.dg_s': assertion 'dg_s_nonneg' still refers to it
|
|
933
|
+
|
|
934
|
+
DROP ASSERTION temp.dg_s_nonneg;
|
|
935
|
+
DROP TABLE temp.dg_s;
|
|
936
|
+
-- run
|
|
937
|
+
|
|
938
|
+
-- --- Cross-schema scoping, the other direction ---
|
|
939
|
+
-- The guard consults assertions in EVERY schema, not only the dropped object's
|
|
940
|
+
-- own: a `temp` assertion naming `main.t` explicitly blocks dropping it, and the
|
|
941
|
+
-- refusal qualifies the assertion so the user can find the thing to drop.
|
|
942
|
+
|
|
943
|
+
CREATE TABLE main.dg_x (x INTEGER PRIMARY KEY);
|
|
944
|
+
CREATE TABLE main.dg_x_other (i INTEGER PRIMARY KEY);
|
|
945
|
+
CREATE ASSERTION temp.dg_x_nonneg CHECK (NOT EXISTS (SELECT 1 FROM main.dg_x WHERE x < 0));
|
|
946
|
+
-- run
|
|
947
|
+
|
|
948
|
+
DROP TABLE main.dg_x;
|
|
949
|
+
-- error: cannot drop table 'main.dg_x': assertion 'temp.dg_x_nonneg' still refers to it
|
|
950
|
+
|
|
951
|
+
-- The refusal is a clean no-op — writes to an unrelated table still commit,
|
|
952
|
+
-- which is exactly what the missed drop used to break.
|
|
953
|
+
INSERT INTO main.dg_x_other VALUES (1);
|
|
954
|
+
COMMIT;
|
|
955
|
+
SELECT COUNT(*) AS n FROM main.dg_x_other;
|
|
956
|
+
→ [{ "n": 1 }]
|
|
957
|
+
|
|
958
|
+
DROP ASSERTION temp.dg_x_nonneg;
|
|
959
|
+
DROP TABLE main.dg_x;
|
|
960
|
+
DROP TABLE main.dg_x_other;
|
|
961
|
+
-- run
|
|
962
|
+
|
|
963
|
+
-- A `temp` assertion whose BARE name has no `temp` table to bind resolves down
|
|
964
|
+
-- its home path to `main` and blocks that drop too.
|
|
965
|
+
CREATE TABLE main.dg_y (x INTEGER PRIMARY KEY);
|
|
966
|
+
CREATE TABLE temp.dg_y_anchor (x INTEGER PRIMARY KEY);
|
|
967
|
+
CREATE ASSERTION temp.dg_y_nonneg CHECK (NOT EXISTS (SELECT 1 FROM dg_y WHERE x < 0));
|
|
968
|
+
-- run
|
|
969
|
+
|
|
970
|
+
DROP TABLE main.dg_y;
|
|
971
|
+
-- error: cannot drop table 'main.dg_y': assertion 'temp.dg_y_nonneg' still refers to it
|
|
972
|
+
|
|
973
|
+
DROP ASSERTION temp.dg_y_nonneg;
|
|
974
|
+
DROP TABLE main.dg_y;
|
|
975
|
+
DROP TABLE temp.dg_y_anchor;
|
|
976
|
+
-- run
|
|
977
|
+
|
|
978
|
+
-- --- The assertion reaches the object THROUGH a view ---
|
|
979
|
+
-- An assertion body names a view; the view's body names the table. Dropping the
|
|
980
|
+
-- table used to be accepted and then made every write to the whole database fail
|
|
981
|
+
-- with an error naming neither the assertion nor the view.
|
|
982
|
+
|
|
983
|
+
CREATE TABLE dg_c (x INTEGER PRIMARY KEY);
|
|
984
|
+
CREATE VIEW dg_cv AS SELECT * FROM dg_c;
|
|
985
|
+
CREATE TABLE dg_c_other (i INTEGER PRIMARY KEY);
|
|
986
|
+
CREATE ASSERTION dg_c_nonneg CHECK (NOT EXISTS (SELECT 1 FROM dg_cv WHERE x < 0));
|
|
987
|
+
-- run
|
|
988
|
+
|
|
989
|
+
-- The message names the chain: an assertion whose body does not mention `dg_c`
|
|
990
|
+
-- is unactionable advice without it.
|
|
991
|
+
DROP TABLE dg_c;
|
|
992
|
+
-- error: cannot drop table 'main.dg_c': assertion 'dg_c_nonneg' reaches it through view 'dg_cv'
|
|
993
|
+
|
|
994
|
+
-- Still a clean no-op, and the unrelated table is still writable.
|
|
995
|
+
INSERT INTO dg_c_other VALUES (1);
|
|
996
|
+
COMMIT;
|
|
997
|
+
SELECT COUNT(*) AS n FROM dg_c_other;
|
|
998
|
+
→ [{ "n": 1 }]
|
|
999
|
+
|
|
1000
|
+
DROP ASSERTION dg_c_nonneg;
|
|
1001
|
+
DROP TABLE dg_c;
|
|
1002
|
+
DROP VIEW dg_cv;
|
|
1003
|
+
DROP TABLE dg_c_other;
|
|
1004
|
+
-- run
|
|
1005
|
+
|
|
1006
|
+
-- --- Three levels deep, and the intermediate views are guarded too ---
|
|
1007
|
+
-- One-hop expansion is the natural under-fix; the chain here is
|
|
1008
|
+
-- assertion → dg_d2 → dg_d1 → dg_d.
|
|
1009
|
+
|
|
1010
|
+
CREATE TABLE dg_d (x INTEGER PRIMARY KEY);
|
|
1011
|
+
CREATE VIEW dg_d1 AS SELECT * FROM dg_d;
|
|
1012
|
+
CREATE VIEW dg_d2 AS SELECT * FROM dg_d1;
|
|
1013
|
+
CREATE ASSERTION dg_d_nonneg CHECK (NOT EXISTS (SELECT 1 FROM dg_d2 WHERE x < 0));
|
|
1014
|
+
-- run
|
|
1015
|
+
|
|
1016
|
+
DROP TABLE dg_d;
|
|
1017
|
+
-- error: cannot drop table 'main.dg_d': assertion 'dg_d_nonneg' reaches it through view 'dg_d2' -> view 'dg_d1'
|
|
1018
|
+
|
|
1019
|
+
-- A view reached only through a further view is refused on the same closure.
|
|
1020
|
+
DROP VIEW dg_d1;
|
|
1021
|
+
-- error: cannot drop view 'main.dg_d1': assertion 'dg_d_nonneg' reaches it through view 'dg_d2'
|
|
1022
|
+
|
|
1023
|
+
-- The one the assertion names directly keeps the direct wording.
|
|
1024
|
+
DROP VIEW dg_d2;
|
|
1025
|
+
-- error: cannot drop view 'main.dg_d2': assertion 'dg_d_nonneg' still refers to it
|
|
1026
|
+
|
|
1027
|
+
-- Dropping the rule is still the whole escape hatch, at any depth.
|
|
1028
|
+
DROP ASSERTION dg_d_nonneg;
|
|
1029
|
+
DROP VIEW dg_d2;
|
|
1030
|
+
DROP VIEW dg_d1;
|
|
1031
|
+
DROP TABLE dg_d;
|
|
1032
|
+
-- run
|
|
1033
|
+
|
|
1034
|
+
-- --- A materialized view in the chain ---
|
|
1035
|
+
|
|
1036
|
+
CREATE TABLE dg_e (x INTEGER PRIMARY KEY);
|
|
1037
|
+
CREATE MATERIALIZED VIEW dg_emv AS SELECT x FROM dg_e;
|
|
1038
|
+
CREATE VIEW dg_ev AS SELECT * FROM dg_emv;
|
|
1039
|
+
CREATE ASSERTION dg_e_nonneg CHECK (NOT EXISTS (SELECT 1 FROM dg_ev WHERE x < 0));
|
|
1040
|
+
-- run
|
|
1041
|
+
|
|
1042
|
+
DROP TABLE dg_e;
|
|
1043
|
+
-- error: cannot drop table 'main.dg_e': assertion 'dg_e_nonneg' reaches it through view 'dg_ev' -> materialized view 'dg_emv'
|
|
1044
|
+
|
|
1045
|
+
-- The maintained table in the middle is guarded on both of its drop verbs.
|
|
1046
|
+
DROP MATERIALIZED VIEW dg_emv;
|
|
1047
|
+
-- error: cannot drop materialized view 'main.dg_emv': assertion 'dg_e_nonneg' reaches it through view 'dg_ev'
|
|
1048
|
+
|
|
1049
|
+
DROP TABLE dg_emv;
|
|
1050
|
+
-- error: cannot drop table 'main.dg_emv': assertion 'dg_e_nonneg' reaches it through view 'dg_ev'
|
|
1051
|
+
|
|
1052
|
+
DROP ASSERTION dg_e_nonneg;
|
|
1053
|
+
DROP VIEW dg_ev;
|
|
1054
|
+
DROP MATERIALIZED VIEW dg_emv;
|
|
1055
|
+
DROP TABLE dg_e;
|
|
1056
|
+
-- run
|
|
1057
|
+
|
|
1058
|
+
-- --- A merely SHADOWED name is still not a reference ---
|
|
1059
|
+
-- The closure collects every reference the traversal reports, and that traversal
|
|
1060
|
+
-- is scope-aware — a `with dg_f as (…)` that shadows the name binds the CTE, not
|
|
1061
|
+
-- the real table, so it must not turn a legal drop into an error. This is the
|
|
1062
|
+
-- property a collect-everything walk would silently lose.
|
|
1063
|
+
|
|
1064
|
+
CREATE TABLE dg_f (x INTEGER PRIMARY KEY);
|
|
1065
|
+
CREATE TABLE dg_f_src (x INTEGER PRIMARY KEY);
|
|
1066
|
+
CREATE ASSERTION dg_f_nonneg CHECK (NOT EXISTS (
|
|
1067
|
+
WITH dg_f AS (SELECT x FROM dg_f_src) SELECT 1 FROM dg_f WHERE x < 0));
|
|
1068
|
+
-- run
|
|
1069
|
+
|
|
1070
|
+
DROP TABLE dg_f;
|
|
1071
|
+
-- run
|
|
1072
|
+
|
|
1073
|
+
-- ...and the table the CTE actually reads IS still protected.
|
|
1074
|
+
DROP TABLE dg_f_src;
|
|
1075
|
+
-- error: cannot drop table 'main.dg_f_src': assertion 'dg_f_nonneg' still refers to it
|
|
1076
|
+
|
|
1077
|
+
DROP ASSERTION dg_f_nonneg;
|
|
1078
|
+
DROP TABLE dg_f_src;
|
|
1079
|
+
-- run
|
|
1080
|
+
|
|
1081
|
+
-- --- A plain view over a dropped table, with no assertion reading it ---
|
|
1082
|
+
-- The engine's stated asymmetry survives the widening: without an assertion in
|
|
1083
|
+
-- the picture the view just breaks and the drop stands.
|
|
1084
|
+
|
|
1085
|
+
CREATE TABLE dg_g (x INTEGER PRIMARY KEY);
|
|
1086
|
+
CREATE VIEW dg_gv AS SELECT * FROM dg_g;
|
|
1087
|
+
-- run
|
|
1088
|
+
|
|
1089
|
+
DROP TABLE dg_g;
|
|
1090
|
+
DROP VIEW dg_gv;
|
|
1091
|
+
-- run
|
|
1092
|
+
|
|
1093
|
+
-- --- The two widenings composed: another schema's assertion, through a view ---
|
|
1094
|
+
-- Cross-schema scope and chain-following are independent mechanisms; only their
|
|
1095
|
+
-- composition proves the closure is driven by the ASSERTION's home schema and each
|
|
1096
|
+
-- reached body by its OWN. Here `temp.dg_h_nonneg` reads `main.dg_hv`, whose bare
|
|
1097
|
+
-- `dg_h` must bind main's — even though the root assertion lives in temp.
|
|
1098
|
+
|
|
1099
|
+
CREATE TABLE main.dg_h (x INTEGER PRIMARY KEY);
|
|
1100
|
+
CREATE VIEW main.dg_hv AS SELECT * FROM dg_h;
|
|
1101
|
+
CREATE ASSERTION temp.dg_h_nonneg CHECK (NOT EXISTS (SELECT 1 FROM main.dg_hv WHERE x < 0));
|
|
1102
|
+
-- run
|
|
1103
|
+
|
|
1104
|
+
DROP TABLE main.dg_h;
|
|
1105
|
+
-- error: cannot drop table 'main.dg_h': assertion 'temp.dg_h_nonneg' reaches it through view 'dg_hv'
|
|
1106
|
+
|
|
1107
|
+
DROP ASSERTION temp.dg_h_nonneg;
|
|
1108
|
+
DROP VIEW main.dg_hv;
|
|
1109
|
+
DROP TABLE main.dg_h;
|
|
1110
|
+
-- run
|
|
1111
|
+
|
|
1112
|
+
-- ...and a temp table of the same name is NOT what the main-owned view body reads,
|
|
1113
|
+
-- so dropping it is no business of that assertion's.
|
|
1114
|
+
CREATE TABLE main.dg_i (x INTEGER PRIMARY KEY);
|
|
1115
|
+
CREATE TABLE temp.dg_i (x INTEGER PRIMARY KEY);
|
|
1116
|
+
CREATE VIEW main.dg_iv AS SELECT * FROM dg_i;
|
|
1117
|
+
CREATE ASSERTION temp.dg_i_nonneg CHECK (NOT EXISTS (SELECT 1 FROM main.dg_iv WHERE x < 0));
|
|
1118
|
+
-- run
|
|
1119
|
+
|
|
1120
|
+
DROP TABLE temp.dg_i;
|
|
1121
|
+
-- run
|
|
1122
|
+
|
|
1123
|
+
DROP TABLE main.dg_i;
|
|
1124
|
+
-- error: cannot drop table 'main.dg_i': assertion 'temp.dg_i_nonneg' reaches it through view 'dg_iv'
|
|
1125
|
+
|
|
1126
|
+
DROP ASSERTION temp.dg_i_nonneg;
|
|
1127
|
+
DROP VIEW main.dg_iv;
|
|
1128
|
+
DROP TABLE main.dg_i;
|
|
1129
|
+
-- run
|
|
1130
|
+
|
|
1131
|
+
-- --- The guard refuses only what the assertion actually names ---
|
|
1132
|
+
-- A live assertion must not make every drop in its schema fail.
|
|
1133
|
+
|
|
1134
|
+
CREATE TABLE dg_named (x INTEGER PRIMARY KEY);
|
|
1135
|
+
CREATE TABLE dg_unrelated (i INTEGER PRIMARY KEY);
|
|
1136
|
+
CREATE VIEW dg_unrelated_v AS SELECT i FROM dg_unrelated;
|
|
1137
|
+
CREATE ASSERTION dg_named_nonneg CHECK (NOT EXISTS (SELECT 1 FROM dg_named WHERE x < 0));
|
|
1138
|
+
-- run
|
|
1139
|
+
|
|
1140
|
+
DROP VIEW dg_unrelated_v;
|
|
1141
|
+
DROP TABLE dg_unrelated;
|
|
1142
|
+
-- run
|
|
1143
|
+
|
|
1144
|
+
-- The refusal reports the catalog's spelling of the schema, not the caller's.
|
|
1145
|
+
DROP TABLE MAIN.dg_named;
|
|
1146
|
+
-- error: cannot drop table 'main.dg_named': assertion 'dg_named_nonneg' still refers to it
|
|
1147
|
+
|
|
1148
|
+
DROP ASSERTION dg_named_nonneg;
|
|
1149
|
+
DROP TABLE dg_named;
|
|
1150
|
+
-- run
|
|
1151
|
+
|
|
1152
|
+
-- ===================================
|
|
1153
|
+
-- Assertions over MAINTAINED TABLES (materialized views) are enforced
|
|
1154
|
+
-- ===================================
|
|
1155
|
+
-- A materialized view IS a table, so an assertion body naming one compiles to a plan
|
|
1156
|
+
-- reading that maintained table. The COMMIT-time evaluator dispatches an assertion only
|
|
1157
|
+
-- when the base tables its plan reads overlap the set of base tables the transaction
|
|
1158
|
+
-- changed, so the maintained table has to show up in the transaction change log.
|
|
1159
|
+
--
|
|
1160
|
+
-- Row-time maintenance writes the backing through the privileged BackingHost surface,
|
|
1161
|
+
-- which does not touch the change log — so maintenance records each REALIZED backing
|
|
1162
|
+
-- delta into it explicitly (MaterializedViewManager.recordMaintenanceChanges). Without
|
|
1163
|
+
-- that recording, every violating commit below succeeded silently.
|
|
1164
|
+
--
|
|
1165
|
+
-- Note: `REFRESH MATERIALIZED VIEW` deliberately stays outside this — it swaps committed
|
|
1166
|
+
-- contents wholesale without routing through row-time maintenance, so a refresh that
|
|
1167
|
+
-- re-derives violating content does not trip an assertion at that commit. That is a
|
|
1168
|
+
-- separate, pre-existing gap, not covered here.
|
|
1169
|
+
|
|
1170
|
+
-- --- Projection MV: a write to the SOURCE trips the assertion ---
|
|
1171
|
+
|
|
1172
|
+
create table amv_w (x integer primary key);
|
|
1173
|
+
create materialized view amv_mv as select x from amv_w;
|
|
1174
|
+
create assertion amv_nonneg check (not exists (select 1 from amv_mv where x < 0));
|
|
1175
|
+
commit;
|
|
1176
|
+
-- run
|
|
1177
|
+
|
|
1178
|
+
begin;
|
|
1179
|
+
insert into amv_w values (-1);
|
|
1180
|
+
commit;
|
|
1181
|
+
-- error: Integrity assertion failed: amv_nonneg
|
|
1182
|
+
|
|
1183
|
+
-- The whole transaction rolled back — neither the source nor the view kept the row.
|
|
1184
|
+
select count(*) as c from amv_w;
|
|
1185
|
+
→ [{ "c": 0 }]
|
|
1186
|
+
|
|
1187
|
+
select count(*) as c from amv_mv;
|
|
1188
|
+
→ [{ "c": 0 }]
|
|
1189
|
+
|
|
1190
|
+
-- --- The same assertion fires on a LATER commit, not only the first ---
|
|
1191
|
+
-- The first COMMIT after CREATE ASSERTION compiles the assertion's plan and may fall back
|
|
1192
|
+
-- to global re-evaluation (its projection capture demand is registered too late for that
|
|
1193
|
+
-- transaction). A later commit takes the ordinary keyed-dispatch path; it must still fire.
|
|
1194
|
+
|
|
1195
|
+
begin;
|
|
1196
|
+
insert into amv_w values (1), (2);
|
|
1197
|
+
commit;
|
|
1198
|
+
-- run
|
|
1199
|
+
|
|
1200
|
+
select count(*) as c from amv_mv;
|
|
1201
|
+
→ [{ "c": 2 }]
|
|
1202
|
+
|
|
1203
|
+
begin;
|
|
1204
|
+
insert into amv_w values (-5);
|
|
1205
|
+
commit;
|
|
1206
|
+
-- error: Integrity assertion failed: amv_nonneg
|
|
1207
|
+
|
|
1208
|
+
-- --- A write-through to the MV ITSELF trips it too ---
|
|
1209
|
+
-- The write is rewritten onto the source, so it reaches the assertion by the same seam.
|
|
1210
|
+
|
|
1211
|
+
begin;
|
|
1212
|
+
insert into amv_mv values (-3);
|
|
1213
|
+
commit;
|
|
1214
|
+
-- error: Integrity assertion failed: amv_nonneg
|
|
1215
|
+
|
|
1216
|
+
-- --- Negative: writes that keep the view clean still commit ---
|
|
1217
|
+
|
|
1218
|
+
begin;
|
|
1219
|
+
insert into amv_w values (7);
|
|
1220
|
+
commit;
|
|
1221
|
+
-- run
|
|
1222
|
+
|
|
1223
|
+
begin;
|
|
1224
|
+
update amv_w set x = 8 where x = 7;
|
|
1225
|
+
commit;
|
|
1226
|
+
-- run
|
|
1227
|
+
|
|
1228
|
+
begin;
|
|
1229
|
+
delete from amv_w where x = 8;
|
|
1230
|
+
commit;
|
|
1231
|
+
-- run
|
|
1232
|
+
|
|
1233
|
+
select x from amv_mv order by x;
|
|
1234
|
+
→ [{ "x": 1 }, { "x": 2 }]
|
|
1235
|
+
|
|
1236
|
+
-- --- A savepoint-discarded MV delta does not trip the assertion ---
|
|
1237
|
+
-- ROLLBACK TO discards the change-log layer the maintenance delta was recorded into, so
|
|
1238
|
+
-- the following clean COMMIT sees no violating view row.
|
|
1239
|
+
|
|
1240
|
+
begin;
|
|
1241
|
+
savepoint sp1;
|
|
1242
|
+
insert into amv_w values (-9);
|
|
1243
|
+
rollback to sp1;
|
|
1244
|
+
insert into amv_w values (9);
|
|
1245
|
+
commit;
|
|
1246
|
+
-- run
|
|
1247
|
+
|
|
1248
|
+
select x from amv_mv order by x;
|
|
1249
|
+
→ [{ "x": 1 }, { "x": 2 }, { "x": 9 }]
|
|
1250
|
+
|
|
1251
|
+
drop assertion amv_nonneg;
|
|
1252
|
+
drop materialized view amv_mv;
|
|
1253
|
+
drop table amv_w;
|
|
1254
|
+
-- run
|
|
1255
|
+
|
|
1256
|
+
-- --- Aggregate MV (residual / delta-aggregate arm, flushed end-of-statement) ---
|
|
1257
|
+
|
|
1258
|
+
create table amv_t (id integer primary key, g text, amt integer);
|
|
1259
|
+
create materialized view amv_agg as select g, sum(amt) as total from amv_t group by g;
|
|
1260
|
+
create assertion amv_cap check (not exists (select 1 from amv_agg where total > 100));
|
|
1261
|
+
commit;
|
|
1262
|
+
-- run
|
|
1263
|
+
|
|
1264
|
+
begin;
|
|
1265
|
+
insert into amv_t values (1, 'a', 40);
|
|
1266
|
+
commit;
|
|
1267
|
+
-- run
|
|
1268
|
+
|
|
1269
|
+
select g, total from amv_agg order by g;
|
|
1270
|
+
→ [{ "g": "a", "total": 40 }]
|
|
1271
|
+
|
|
1272
|
+
-- Each row is under the cap; only the maintained SUM crosses it.
|
|
1273
|
+
begin;
|
|
1274
|
+
insert into amv_t values (2, 'a', 70);
|
|
1275
|
+
commit;
|
|
1276
|
+
-- error: Integrity assertion failed: amv_cap
|
|
1277
|
+
|
|
1278
|
+
-- An update that pushes an existing group over the cap trips it too.
|
|
1279
|
+
begin;
|
|
1280
|
+
update amv_t set amt = 200 where id = 1;
|
|
1281
|
+
commit;
|
|
1282
|
+
-- error: Integrity assertion failed: amv_cap
|
|
1283
|
+
|
|
1284
|
+
select g, total from amv_agg order by g;
|
|
1285
|
+
→ [{ "g": "a", "total": 40 }]
|
|
1286
|
+
|
|
1287
|
+
drop assertion amv_cap;
|
|
1288
|
+
drop materialized view amv_agg;
|
|
1289
|
+
drop table amv_t;
|
|
1290
|
+
-- run
|
|
1291
|
+
|
|
1292
|
+
-- --- MV over MV: the chain's TAIL is what the assertion names ---
|
|
1293
|
+
-- The consumer's backing is written only by the cascade off its producer's realized
|
|
1294
|
+
-- delta, so this case needs the recording at BOTH levels.
|
|
1295
|
+
|
|
1296
|
+
create table amv_c (x integer primary key);
|
|
1297
|
+
create materialized view amv_c1 as select x from amv_c;
|
|
1298
|
+
create materialized view amv_c2 as select x from amv_c1;
|
|
1299
|
+
create assertion amv_chain check (not exists (select 1 from amv_c2 where x < 0));
|
|
1300
|
+
commit;
|
|
1301
|
+
-- run
|
|
1302
|
+
|
|
1303
|
+
begin;
|
|
1304
|
+
insert into amv_c values (-1);
|
|
1305
|
+
commit;
|
|
1306
|
+
-- error: Integrity assertion failed: amv_chain
|
|
1307
|
+
|
|
1308
|
+
begin;
|
|
1309
|
+
insert into amv_c values (4);
|
|
1310
|
+
commit;
|
|
1311
|
+
-- run
|
|
1312
|
+
|
|
1313
|
+
select x from amv_c2 order by x;
|
|
1314
|
+
→ [{ "x": 4 }]
|
|
1315
|
+
|
|
1316
|
+
drop assertion amv_chain;
|
|
1317
|
+
drop materialized view amv_c2;
|
|
1318
|
+
drop materialized view amv_c1;
|
|
1319
|
+
drop table amv_c;
|
|
1320
|
+
-- run
|
|
1321
|
+
|
|
1322
|
+
-- --- `create table … maintained as` reaches the same seam ---
|
|
1323
|
+
-- MV sugar and an explicitly-declared maintained table share one maintenance path.
|
|
1324
|
+
|
|
1325
|
+
create table amv_src (id integer primary key, n integer);
|
|
1326
|
+
create table amv_mt (id integer primary key, n integer) maintained as select id, n from amv_src;
|
|
1327
|
+
create assertion amv_mt_pos check (not exists (select 1 from amv_mt where n < 0));
|
|
1328
|
+
commit;
|
|
1329
|
+
-- run
|
|
1330
|
+
|
|
1331
|
+
begin;
|
|
1332
|
+
insert into amv_src values (1, -2);
|
|
1333
|
+
commit;
|
|
1334
|
+
-- error: Integrity assertion failed: amv_mt_pos
|
|
1335
|
+
|
|
1336
|
+
begin;
|
|
1337
|
+
insert into amv_src values (1, 2);
|
|
1338
|
+
commit;
|
|
1339
|
+
-- run
|
|
1340
|
+
|
|
1341
|
+
select id, n from amv_mt order by id;
|
|
1342
|
+
→ [{ "id": 1, "n": 2 }]
|
|
1343
|
+
|
|
1344
|
+
drop assertion amv_mt_pos;
|
|
1345
|
+
drop table amv_mt;
|
|
1346
|
+
drop table amv_src;
|
|
1347
|
+
-- run
|
|
1348
|
+
|
|
1349
|
+
-- --- Partial (WHERE-filtered) MV: a row LEAVING the view's scope records a DELETE ---
|
|
1350
|
+
-- The cases above are all driven by insert/update deltas. A row that stops satisfying the
|
|
1351
|
+
-- body's WHERE is evicted from the backing, and only recording that delete makes the
|
|
1352
|
+
-- maintained table a changed base at COMMIT.
|
|
1353
|
+
|
|
1354
|
+
create table pw_t (id integer primary key, active integer, amt integer);
|
|
1355
|
+
create materialized view pw_mv as select id, amt from pw_t where active = 1;
|
|
1356
|
+
insert into pw_t values (1, 1, 5);
|
|
1357
|
+
create assertion pw_nonempty check (exists (select 1 from pw_mv));
|
|
1358
|
+
commit;
|
|
1359
|
+
-- run
|
|
1360
|
+
|
|
1361
|
+
-- Warm-up past the CREATE-ASSERTION commit, which falls back to global re-evaluation.
|
|
1362
|
+
begin;
|
|
1363
|
+
insert into pw_t values (2, 1, 6);
|
|
1364
|
+
commit;
|
|
1365
|
+
-- run
|
|
1366
|
+
|
|
1367
|
+
select id, amt from pw_mv order by id;
|
|
1368
|
+
→ [{ "id": 1, "amt": 5 }, { "id": 2, "amt": 6 }]
|
|
1369
|
+
|
|
1370
|
+
-- Deactivating every row evicts both from the view; the realized delta is a pair of deletes.
|
|
1371
|
+
begin;
|
|
1372
|
+
update pw_t set active = 0;
|
|
1373
|
+
commit;
|
|
1374
|
+
-- error: Integrity assertion failed: pw_nonempty
|
|
1375
|
+
|
|
1376
|
+
select id, amt from pw_mv order by id;
|
|
1377
|
+
→ [{ "id": 1, "amt": 5 }, { "id": 2, "amt": 6 }]
|
|
1378
|
+
|
|
1379
|
+
drop assertion pw_nonempty;
|
|
1380
|
+
drop materialized view pw_mv;
|
|
1381
|
+
drop table pw_t;
|
|
1382
|
+
-- run
|
|
1383
|
+
|
|
1384
|
+
-- --- RENAME COLUMN cascades into an assertion that reads the table THROUGH A VIEW ---
|
|
1385
|
+
-- Rewriting the view's body shifts the name it publishes (x → y), and the assertion
|
|
1386
|
+
-- reads the VIEW — one level further out. Pre-fix the assertion kept naming `x`,
|
|
1387
|
+
-- and because every live assertion recompiles on any commit, EVERY write to the
|
|
1388
|
+
-- database failed after the rename, not just writes near the view.
|
|
1389
|
+
|
|
1390
|
+
create table arc_w (id integer primary key, x integer);
|
|
1391
|
+
create view arc_v1 as select id, x from arc_w;
|
|
1392
|
+
create assertion arc_a1 check (not exists (select 1 from arc_v1 where x < 0));
|
|
1393
|
+
commit;
|
|
1394
|
+
-- run
|
|
1395
|
+
|
|
1396
|
+
alter table arc_w rename column x to y;
|
|
1397
|
+
-- run
|
|
1398
|
+
|
|
1399
|
+
-- writes work again after the rename…
|
|
1400
|
+
begin;
|
|
1401
|
+
insert into arc_w values (1, 1);
|
|
1402
|
+
commit;
|
|
1403
|
+
-- run
|
|
1404
|
+
|
|
1405
|
+
-- …and the assertion still enforces the same rule through the rewritten view
|
|
1406
|
+
begin;
|
|
1407
|
+
insert into arc_w values (2, -1);
|
|
1408
|
+
commit;
|
|
1409
|
+
-- error: Integrity assertion failed: arc_a1
|
|
1410
|
+
|
|
1411
|
+
select id, y from arc_w order by id;
|
|
1412
|
+
→ [{ "id": 1, "y": 1 }]
|
|
1413
|
+
|
|
1414
|
+
drop assertion arc_a1;
|
|
1415
|
+
drop view arc_v1;
|
|
1416
|
+
drop table arc_w;
|
|
1417
|
+
-- run
|