@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.
Files changed (400) hide show
  1. package/README.md +2 -0
  2. package/dist/src/common/types.d.ts +4 -4
  3. package/dist/src/index.d.ts +1 -1
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/index.js +3 -2
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/schema/catalog-rendering.js +13 -1
  8. package/dist/src/schema/catalog-rendering.js.map +1 -1
  9. package/dist/src/schema/catalog.d.ts +35 -2
  10. package/dist/src/schema/catalog.d.ts.map +1 -1
  11. package/dist/src/schema/catalog.js +42 -33
  12. package/dist/src/schema/catalog.js.map +1 -1
  13. package/dist/src/schema/schema-differ.d.ts +1 -1
  14. package/dist/src/schema/schema-differ.d.ts.map +1 -1
  15. package/dist/src/schema/schema-differ.js +195 -33
  16. package/dist/src/schema/schema-differ.js.map +1 -1
  17. package/dist/src/schema/unique-enforcement.d.ts +23 -1
  18. package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
  19. package/dist/src/schema/unique-enforcement.js +43 -1
  20. package/dist/src/schema/unique-enforcement.js.map +1 -1
  21. package/dist/src/vtab/memory/layer/manager.d.ts +15 -12
  22. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  23. package/dist/src/vtab/memory/layer/manager.js +87 -100
  24. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  25. package/package.json +7 -4
  26. package/test/README.md +173 -0
  27. package/test/logic/01-basic.sqllogic +39 -0
  28. package/test/logic/01.1-select-projection-extras.sqllogic +130 -0
  29. package/test/logic/01.2-strings.sqllogic +43 -0
  30. package/test/logic/01.5-insert-select.sqllogic +237 -0
  31. package/test/logic/01.6-update-extras.sqllogic +118 -0
  32. package/test/logic/01.7-update-from.sqllogic +67 -0
  33. package/test/logic/01.8-delete-extras.sqllogic +122 -0
  34. package/test/logic/01.8.1-delete-range-cow.sqllogic +115 -0
  35. package/test/logic/01.9-query-expr-dml-gates.sqllogic +32 -0
  36. package/test/logic/01.9-query-expr-dml.sqllogic +171 -0
  37. package/test/logic/01.9-query-expr-values.sqllogic +111 -0
  38. package/test/logic/01.9.1-limit-over-dml-subquery.sqllogic +119 -0
  39. package/test/logic/02-filters.sqllogic +54 -0
  40. package/test/logic/02-smoke.sqllogic +62 -0
  41. package/test/logic/02.1-bind-parameters.sqllogic +223 -0
  42. package/test/logic/03-expressions.sqllogic +190 -0
  43. package/test/logic/03.1-quoted-identifiers.sqllogic +55 -0
  44. package/test/logic/03.4-defaults.sqllogic +554 -0
  45. package/test/logic/03.4.1-default-edge-cases.sqllogic +99 -0
  46. package/test/logic/03.5-tvf.sqllogic +61 -0
  47. package/test/logic/03.6-type-system.sqllogic +544 -0
  48. package/test/logic/03.6.1-numeric-text-comparison-coercion.sqllogic +244 -0
  49. package/test/logic/03.6.2-value-to-text.sqllogic +224 -0
  50. package/test/logic/03.7-bigint-mixed-arithmetic.sqllogic +65 -0
  51. package/test/logic/03.8-not-precedence.sqllogic +255 -0
  52. package/test/logic/03.9-is-bool-predicate.sqllogic +150 -0
  53. package/test/logic/03.9.1-is-bool-predicate-partial-index.sqllogic +34 -0
  54. package/test/logic/04-order-by.sqllogic +38 -0
  55. package/test/logic/04-transactions.sqllogic +239 -0
  56. package/test/logic/04a-savepoint-lazy-attach.sqllogic +122 -0
  57. package/test/logic/05-vtab_memory.sqllogic +50 -0
  58. package/test/logic/05.0.1-vtab-memory-unique-index-collation.sqllogic +34 -0
  59. package/test/logic/05.1-composite-pk-range-scan.sqllogic +147 -0
  60. package/test/logic/05.1.1-secondary-index-range-scan.sqllogic +56 -0
  61. package/test/logic/05.2-cast-seek-correctness.sqllogic +124 -0
  62. package/test/logic/06-builtin_functions.sqllogic +149 -0
  63. package/test/logic/06.1-string-functions.sqllogic +92 -0
  64. package/test/logic/06.1.3-like-glob-edges.sqllogic +53 -0
  65. package/test/logic/06.2-math-functions.sqllogic +82 -0
  66. package/test/logic/06.3-schema.sqllogic +48 -0
  67. package/test/logic/06.3.1-schema-all-schemas.sqllogic +39 -0
  68. package/test/logic/06.3.2-schema-foreign-keys.sqllogic +208 -0
  69. package/test/logic/06.3.3-introspection-tags.sqllogic +202 -0
  70. package/test/logic/06.3.3.1-introspection-tags-index.sqllogic +74 -0
  71. package/test/logic/06.3.4-view-info.sqllogic +378 -0
  72. package/test/logic/06.3.5-column-info.sqllogic +347 -0
  73. package/test/logic/06.3.6-schema-views.sqllogic +29 -0
  74. package/test/logic/06.4-schema-search-path.sqllogic +354 -0
  75. package/test/logic/06.4.1-schema-case-insensitive.sqllogic +83 -0
  76. package/test/logic/06.4.2-collation-extras.sqllogic +472 -0
  77. package/test/logic/06.4.2.1-collation-index-access-paths.sqllogic +140 -0
  78. package/test/logic/06.4.3-write-path-collation.sqllogic +99 -0
  79. package/test/logic/06.4.4-comparison-collation-precedence.sqllogic +342 -0
  80. package/test/logic/06.4.5-any-collate-declared-keys.sqllogic +92 -0
  81. package/test/logic/06.5-polymorphic-types.sqllogic +169 -0
  82. package/test/logic/06.5.1-conversion-functions.sqllogic +173 -0
  83. package/test/logic/06.5.2-scalar-minmax.sqllogic +16 -0
  84. package/test/logic/06.5.3-undeclared-return-type-comparison.sqllogic +244 -0
  85. package/test/logic/06.5.4-declared-return-type-builtins.sqllogic +288 -0
  86. package/test/logic/06.6-aggregate-extended.sqllogic +78 -0
  87. package/test/logic/06.7-json-extended.sqllogic +53 -0
  88. package/test/logic/06.8-json-path-operators.sqllogic +99 -0
  89. package/test/logic/06.9-json-canonical-key.sqllogic +65 -0
  90. package/test/logic/06.9.1-json-coerce-once.sqllogic +417 -0
  91. package/test/logic/06.9.1.1-json-coerce-once-index.sqllogic +39 -0
  92. package/test/logic/06.9.2-json-structural-equality.sqllogic +382 -0
  93. package/test/logic/06.9.2.1-json-structural-equality-index.sqllogic +76 -0
  94. package/test/logic/06.9.3-json-index-range-seek.sqllogic +205 -0
  95. package/test/logic/07-aggregates.sqllogic +193 -0
  96. package/test/logic/07.1-aggregate-filter-clause.sqllogic +12 -0
  97. package/test/logic/07.2-aggregate-order-by.sqllogic +11 -0
  98. package/test/logic/07.3-group-by-extras.sqllogic +602 -0
  99. package/test/logic/07.3.1-group-by-order-by-key.sqllogic +164 -0
  100. package/test/logic/07.3.2-grouped-select-list-shape.sqllogic +231 -0
  101. package/test/logic/07.4-group-by-filter-composite-pk.sqllogic +120 -0
  102. package/test/logic/07.5-sum-mixed-exact-and-float.sqllogic +151 -0
  103. package/test/logic/07.5-window.sqllogic +1941 -0
  104. package/test/logic/07.5.1-window-named.sqllogic +13 -0
  105. package/test/logic/07.5.2-window-nth-value.sqllogic +13 -0
  106. package/test/logic/07.6-subqueries.sqllogic +78 -0
  107. package/test/logic/07.6.1-subquery-extras.sqllogic +93 -0
  108. package/test/logic/07.7-and-or-short-circuit.sqllogic +58 -0
  109. package/test/logic/07.7-in-subquery-caching.sqllogic +174 -0
  110. package/test/logic/07.7-scalar-agg-decorrelation.sqllogic +197 -0
  111. package/test/logic/07.7.1-in-extras.sqllogic +158 -0
  112. package/test/logic/07.7.1-scalar-agg-decorrelation-filter.sqllogic +105 -0
  113. package/test/logic/07.7.2-scalar-agg-decorrelation-nested.sqllogic +115 -0
  114. package/test/logic/07.7.3-exists-in-select-decorrelation.sqllogic +107 -0
  115. package/test/logic/07.7.4-where-conjunct-ordering.sqllogic +121 -0
  116. package/test/logic/07.7.5-filter-lost-under-index-order.sqllogic +134 -0
  117. package/test/logic/07.7.6-correlated-predicate-scope.sqllogic +178 -0
  118. package/test/logic/07.7.7-join-source-scope-shadowing.sqllogic +137 -0
  119. package/test/logic/07.7.8-correlated-ref-to-computed-column.sqllogic +165 -0
  120. package/test/logic/07.7.9-conjunct-monotonicity.sqllogic +200 -0
  121. package/test/logic/07.8-correlated-subquery-edges.sqllogic +71 -0
  122. package/test/logic/07.8.1-correlated-in-decorrelation-gates.sqllogic +156 -0
  123. package/test/logic/07.9-in-value-list.sqllogic +154 -0
  124. package/test/logic/07.9.1-in-value-list-composite-index.sqllogic +38 -0
  125. package/test/logic/08-views.sqllogic +64 -0
  126. package/test/logic/08.1-semi-anti-join.sqllogic +191 -0
  127. package/test/logic/08.1-view-edge-cases.sqllogic +118 -0
  128. package/test/logic/08.1.1-uncorrelated-in-semijoin.sqllogic +251 -0
  129. package/test/logic/08.2-existence-flag-semijoin-recovery.sqllogic +197 -0
  130. package/test/logic/08.3-existence-flag-inner-recovery.sqllogic +102 -0
  131. package/test/logic/08.4-key-set-semi-join.sqllogic +447 -0
  132. package/test/logic/09-set_operations.sqllogic +244 -0
  133. package/test/logic/09.1-set-op-cross-collation.sqllogic +230 -0
  134. package/test/logic/10-distinct_datatypes.sqllogic +127 -0
  135. package/test/logic/10.1-ddl-lifecycle.sqllogic +203 -0
  136. package/test/logic/10.1.1-create-table-syntax-edges.sqllogic +47 -0
  137. package/test/logic/10.1.2-ddl-in-transaction.sqllogic +143 -0
  138. package/test/logic/10.1.2.1-add-constraint-in-transaction.sqllogic +59 -0
  139. package/test/logic/10.1.3-ddl-drop-in-transaction.sqllogic +187 -0
  140. package/test/logic/10.1.3.1-drop-constraint-in-transaction.sqllogic +45 -0
  141. package/test/logic/10.1.4-create-table-duplicate-constraint-names.sqllogic +149 -0
  142. package/test/logic/10.1.4-ddl-transaction-policy.sqllogic +314 -0
  143. package/test/logic/10.1.5-alter-add-constraint-minted-name-disambiguation.sqllogic +219 -0
  144. package/test/logic/10.1.5-create-index-lifecycle.sqllogic +72 -0
  145. package/test/logic/10.1.6-ddl-transaction-policy-index.sqllogic +143 -0
  146. package/test/logic/10.1.7-create-table-default-type-check.sqllogic +119 -0
  147. package/test/logic/10.1.8-alter-add-check-existing-rows.sqllogic +117 -0
  148. package/test/logic/10.2-column-features.sqllogic +365 -0
  149. package/test/logic/10.2.1-table-options-rejection.sqllogic +63 -0
  150. package/test/logic/10.2.2-default-collation-memory.sqllogic +30 -0
  151. package/test/logic/10.3-function-features.sqllogic +247 -0
  152. package/test/logic/10.4-schema-scale.sqllogic +168 -0
  153. package/test/logic/10.4.1-schema-scale-indexes.sqllogic +121 -0
  154. package/test/logic/10.5-indexes.sqllogic +164 -0
  155. package/test/logic/10.5.1-partial-indexes.sqllogic +526 -0
  156. package/test/logic/10.5.2-expression-indexes.sqllogic +34 -0
  157. package/test/logic/10.5.3-desc-index-ordering.sqllogic +130 -0
  158. package/test/logic/10.5.4-composite-pk-index-update-phantom.sqllogic +55 -0
  159. package/test/logic/10.5.5-index-name-uniqueness.sqllogic +76 -0
  160. package/test/logic/10.5.6-minmax-index-boundary.sqllogic +204 -0
  161. package/test/logic/10.5.7-implicit-unique-index-lifecycle.sqllogic +1216 -0
  162. package/test/logic/10.6-distinct-edge-cases.sqllogic +119 -0
  163. package/test/logic/100-predicate-normalization-edge-cases.sqllogic +238 -0
  164. package/test/logic/100.1-where-extras.sqllogic +163 -0
  165. package/test/logic/100.1.1-where-join-on-order-indexed.sqllogic +35 -0
  166. package/test/logic/101-builtin-mutation-kills.sqllogic +974 -0
  167. package/test/logic/101-transaction-edge-cases.sqllogic +395 -0
  168. package/test/logic/102-schema-catalog-edge-cases.sqllogic +252 -0
  169. package/test/logic/102-unique-constraints.sqllogic +145 -0
  170. package/test/logic/102.0.1-schema-catalog-indexed-table.sqllogic +32 -0
  171. package/test/logic/102.1-unique-edge-cases.sqllogic +111 -0
  172. package/test/logic/102.1.1-unique-index-creation-edge-cases.sqllogic +75 -0
  173. package/test/logic/102.2-unique-collation.sqllogic +181 -0
  174. package/test/logic/102.2.1-unique-collation-index-derived.sqllogic +305 -0
  175. package/test/logic/103-database-options-edge-cases.sqllogic +108 -0
  176. package/test/logic/103.1-pragma-boolean-values.sqllogic +126 -0
  177. package/test/logic/104-emit-mutation-kills.sqllogic +236 -0
  178. package/test/logic/105-vtab-memory-mutation-kills.sqllogic +754 -0
  179. package/test/logic/105.1-vtab-memory-index-mutation-kills.sqllogic +540 -0
  180. package/test/logic/106-constraint-extractor-mutation-kills.sqllogic +350 -0
  181. package/test/logic/107-temporal-arithmetic-mutation-kills.sqllogic +833 -0
  182. package/test/logic/108-cardinality-estimation.sqllogic +146 -0
  183. package/test/logic/108.1-analyze-name-resolution.sqllogic +102 -0
  184. package/test/logic/109-aggregate-physical-selection.sqllogic +103 -0
  185. package/test/logic/11-joins.sqllogic +86 -0
  186. package/test/logic/11.1-join-using.sqllogic +267 -0
  187. package/test/logic/11.2-comma-join.sqllogic +35 -0
  188. package/test/logic/11.3-index-nested-loop-join.sqllogic +305 -0
  189. package/test/logic/11.3-mismatched-collation-join.sqllogic +74 -0
  190. package/test/logic/11.4-hash-join-side-swap.sqllogic +118 -0
  191. package/test/logic/110-scan-emitter-mutation-kills.sqllogic +121 -0
  192. package/test/logic/110.1-scan-emitter-index-scan-kills.sqllogic +47 -0
  193. package/test/logic/12-empty-primary-key.sqllogic +124 -0
  194. package/test/logic/12-join_padding_order.sqllogic +58 -0
  195. package/test/logic/12.1-orderby-index-consumption.sqllogic +40 -0
  196. package/test/logic/13-cte.sqllogic +137 -0
  197. package/test/logic/13.1-cte-multiple-recursive.sqllogic +104 -0
  198. package/test/logic/13.10-cte-qualified-name.sqllogic +136 -0
  199. package/test/logic/13.11-unreferenced-dml-cte.sqllogic +414 -0
  200. package/test/logic/13.12-nested-dml-cte-rejected.sqllogic +280 -0
  201. package/test/logic/13.2-cte-bind-params.sqllogic +98 -0
  202. package/test/logic/13.3-cte-edge-cases.sqllogic +120 -0
  203. package/test/logic/13.4-cte-extras.sqllogic +98 -0
  204. package/test/logic/13.5-cte-join-order.sqllogic +166 -0
  205. package/test/logic/13.6-cte-dml-runs-once.sqllogic +318 -0
  206. package/test/logic/13.7-cte-sibling-visibility.sqllogic +227 -0
  207. package/test/logic/13.8-insert-with-clause-visibility.sqllogic +254 -0
  208. package/test/logic/13.9-schema-authored-cte-isolation.sqllogic +328 -0
  209. package/test/logic/13.9.1-schema-authored-schema-path-isolation.sqllogic +408 -0
  210. package/test/logic/14-utilities.sqllogic +168 -0
  211. package/test/logic/15-timespan.sqllogic +279 -0
  212. package/test/logic/15.1-semantic-ordering.sqllogic +960 -0
  213. package/test/logic/15.1.1-json-check-coercion.sqllogic +178 -0
  214. package/test/logic/15.1.2-semantic-ordering-index.sqllogic +41 -0
  215. package/test/logic/15.1.3-declared-form-write-contract.sqllogic +519 -0
  216. package/test/logic/16-epoch.sqllogic +187 -0
  217. package/test/logic/17-weekday-modifier.sqllogic +78 -0
  218. package/test/logic/18-json-string-escapes.sqllogic +86 -0
  219. package/test/logic/18-strftime-extended-formats.sqllogic +68 -0
  220. package/test/logic/20-empty-single-row.sqllogic +261 -0
  221. package/test/logic/21-null-edge-cases.sqllogic +227 -0
  222. package/test/logic/21.1-where-null-comparisons.sqllogic +151 -0
  223. package/test/logic/21.1.1-where-null-comparisons-secondary-index.sqllogic +35 -0
  224. package/test/logic/21.2-between-edges.sqllogic +157 -0
  225. package/test/logic/22-boundary-values.sqllogic +175 -0
  226. package/test/logic/23-self-joins-duplicates.sqllogic +74 -0
  227. package/test/logic/24-builtin-branches.sqllogic +611 -0
  228. package/test/logic/24.1-substr-extras.sqllogic +75 -0
  229. package/test/logic/24.2-random-extras.sqllogic +39 -0
  230. package/test/logic/25-aggregate-edge-cases.sqllogic +289 -0
  231. package/test/logic/25.1-nested-aggregates.sqllogic +74 -0
  232. package/test/logic/25.2-having-edge-cases.sqllogic +318 -0
  233. package/test/logic/25.3-aggregate-isnull-empty.sqllogic +16 -0
  234. package/test/logic/25.4-groupby-fd-constant-empty.sqllogic +55 -0
  235. package/test/logic/25.5-ungrouped-aggregate-column-free-select-item.sqllogic +196 -0
  236. package/test/logic/26-join-edge-cases.sqllogic +333 -0
  237. package/test/logic/26.1-left-join-isnull-on-notnull.sqllogic +26 -0
  238. package/test/logic/26.2-left-join-on-vs-where.sqllogic +67 -0
  239. package/test/logic/26.3-join-on-constants.sqllogic +61 -0
  240. package/test/logic/26.4-join-on-is-null-nullable.sqllogic +44 -0
  241. package/test/logic/27-window-edge-cases.sqllogic +206 -0
  242. package/test/logic/27.1-window-groups-frame.sqllogic +13 -0
  243. package/test/logic/27.2-window-exclude.sqllogic +13 -0
  244. package/test/logic/27.3-window-json-aggregation.sqllogic +66 -0
  245. package/test/logic/28-set-ops-sort-edge-cases.sqllogic +444 -0
  246. package/test/logic/28.1-compound-limit-offset.sqllogic +143 -0
  247. package/test/logic/28.2-orderby-expression-extras.sqllogic +698 -0
  248. package/test/logic/28.2-set-op-branch-types.sqllogic +217 -0
  249. package/test/logic/29-constraint-edge-cases.sqllogic +442 -0
  250. package/test/logic/29.1-column-level-conflict-clause.sqllogic +171 -0
  251. package/test/logic/29.2-table-level-pk-conflict-clause.sqllogic +113 -0
  252. package/test/logic/40-constraints.sqllogic +418 -0
  253. package/test/logic/40.1-pk-desc-direction.sqllogic +76 -0
  254. package/test/logic/40.2-check-extras.sqllogic +683 -0
  255. package/test/logic/41-alter-table.sqllogic +191 -0
  256. package/test/logic/41-fk-cascade-conflict-and-self-ref.sqllogic +263 -0
  257. package/test/logic/41-fk-cross-schema.sqllogic +155 -0
  258. package/test/logic/41-fk-extended-targets.sqllogic +173 -0
  259. package/test/logic/41-foreign-keys.sqllogic +1001 -0
  260. package/test/logic/41-generated-column-errors.sqllogic +225 -0
  261. package/test/logic/41-generated-column-extras.sqllogic +598 -0
  262. package/test/logic/41-generated-column-scope.sqllogic +329 -0
  263. package/test/logic/41-generated-columns.sqllogic +361 -0
  264. package/test/logic/41.1-alter-pk.sqllogic +230 -0
  265. package/test/logic/41.1-fk-collation-conflict.sqllogic +277 -0
  266. package/test/logic/41.10-alter-drop-column-foreign-key.sqllogic +274 -0
  267. package/test/logic/41.10.1-alter-drop-column-unique-index.sqllogic +169 -0
  268. package/test/logic/41.10.2-alter-drop-column-check-and-assertion.sqllogic +1616 -0
  269. package/test/logic/41.10.3-alter-drop-column-referencing-fk.sqllogic +292 -0
  270. package/test/logic/41.10.4-drop-object-expression-dependents.sqllogic +602 -0
  271. package/test/logic/41.11-deferred-fk-with-rename.sqllogic +232 -0
  272. package/test/logic/41.12-alter-default-coercion.sqllogic +200 -0
  273. package/test/logic/41.13-alter-add-column-generated-backfill.sqllogic +566 -0
  274. package/test/logic/41.14-alter-add-column-subquery-backfill.sqllogic +129 -0
  275. package/test/logic/41.15-alter-column-default-type-check.sqllogic +178 -0
  276. package/test/logic/41.16-fk-unenforceable.sqllogic +364 -0
  277. package/test/logic/41.2-alter-column.sqllogic +382 -0
  278. package/test/logic/41.2.1-alter-column-retype-deleted-row-memory.sqllogic +32 -0
  279. package/test/logic/41.2.2-alter-column-retype-index-rebuild.sqllogic +77 -0
  280. package/test/logic/41.2.3-alter-column-set-not-null-pk-backfill.sqllogic +310 -0
  281. package/test/logic/41.2.3.1-alter-column-set-not-null-pk-backfill-secondary-index.sqllogic +37 -0
  282. package/test/logic/41.3-alter-add-column-unique.sqllogic +184 -0
  283. package/test/logic/41.3-alter-rename-propagation.sqllogic +2352 -0
  284. package/test/logic/41.3.1-alter-rename-index-propagation.sqllogic +94 -0
  285. package/test/logic/41.4-alter-add-column-constraints.sqllogic +855 -0
  286. package/test/logic/41.5-alter-misc.sqllogic +48 -0
  287. package/test/logic/41.5-cross-schema-foreign-keys.sqllogic +253 -0
  288. package/test/logic/41.6-alter-drop-rename-constraint.sqllogic +354 -0
  289. package/test/logic/41.6.1-alter-drop-constraint-index-derived.sqllogic +22 -0
  290. package/test/logic/41.7-alter-column-collate.sqllogic +105 -0
  291. package/test/logic/41.7.1-alter-column-collate-unique.sqllogic +45 -0
  292. package/test/logic/41.7.2-alter-column-collate-unique-store.sqllogic +208 -0
  293. package/test/logic/41.7.2.1-alter-column-collate-unique-index-store.sqllogic +114 -0
  294. package/test/logic/41.7.3-alter-column-retype-unique.sqllogic +293 -0
  295. package/test/logic/41.7.3.1-alter-column-retype-staged-rows-memory.sqllogic +41 -0
  296. package/test/logic/41.7.3.2-alter-column-retype-unique-index.sqllogic +72 -0
  297. package/test/logic/41.7.4-alter-column-retype-semantic-memory.sqllogic +445 -0
  298. package/test/logic/41.7.4.1-alter-column-retype-semantic-index-memory.sqllogic +91 -0
  299. package/test/logic/41.7.5-alter-column-collate-pk-staged-delete-memory.sqllogic +106 -0
  300. package/test/logic/41.8-alter-add-constraint-unique-fk.sqllogic +275 -0
  301. package/test/logic/41.8-alter-savepoint-staged-rows.sqllogic +443 -0
  302. package/test/logic/41.8.1-alter-rename-savepoint-staged-rows.sqllogic +408 -0
  303. package/test/logic/41.9-alter-constraint-body-change.sqllogic +180 -0
  304. package/test/logic/41.9-fk-restrict-batched.sqllogic +290 -0
  305. package/test/logic/42-committed-snapshot.sqllogic +254 -0
  306. package/test/logic/42-returning.sqllogic +159 -0
  307. package/test/logic/42.1-returning-extras.sqllogic +232 -0
  308. package/test/logic/42.2-returning-stored-row.sqllogic +165 -0
  309. package/test/logic/43-default-nullability.sqllogic +131 -0
  310. package/test/logic/43-transition-constraints.sqllogic +341 -0
  311. package/test/logic/43.1-default-collation.sqllogic +140 -0
  312. package/test/logic/43.1-notnull-or-conflict.sqllogic +79 -0
  313. package/test/logic/43.2-deferred-check-new-on-delete.sqllogic +95 -0
  314. package/test/logic/43.3-nullable-primary-key.sqllogic +587 -0
  315. package/test/logic/44-determinism-validation.sqllogic +164 -0
  316. package/test/logic/44-orthogonality.sqllogic +146 -0
  317. package/test/logic/44.1-nondeterministic-schema.sqllogic +316 -0
  318. package/test/logic/45-udf-determinism.sqllogic +39 -0
  319. package/test/logic/46-mutation-context.sqllogic +595 -0
  320. package/test/logic/47-upsert.sqllogic +255 -0
  321. package/test/logic/47.1-upsert-conflict-targets.sqllogic +169 -0
  322. package/test/logic/47.2-replace-and-or-clauses.sqllogic +164 -0
  323. package/test/logic/47.2.1-pk-replace-secondary-unique.sqllogic +374 -0
  324. package/test/logic/47.3-engine-or-fail.sqllogic +122 -0
  325. package/test/logic/47.3-upsert-conflict-target-collation.sqllogic +139 -0
  326. package/test/logic/47.3.1-upsert-conflict-index-derived-collation.sqllogic +25 -0
  327. package/test/logic/47.4-upsert-conflict-target-affinity.sqllogic +59 -0
  328. package/test/logic/47.5-upsert-do-update-constraints.sqllogic +440 -0
  329. package/test/logic/47.6-upsert-do-update-decomposed.sqllogic +100 -0
  330. package/test/logic/49-reference-graph.sqllogic +84 -0
  331. package/test/logic/50-declarative-schema.sqllogic +2550 -0
  332. package/test/logic/50-metadata-tags.sqllogic +1033 -0
  333. package/test/logic/50.0.1-metadata-tags-index.sqllogic +141 -0
  334. package/test/logic/50.1-declare-schema-pk.sqllogic +150 -0
  335. package/test/logic/50.2-declare-schema-renames.sqllogic +949 -0
  336. package/test/logic/50.2.1-declare-schema-index-renames.sqllogic +173 -0
  337. package/test/logic/50.3-declare-schema-duplicate-names.sqllogic +280 -0
  338. package/test/logic/50.4-declare-schema-apply-restore.sqllogic +192 -0
  339. package/test/logic/51-lens-foundation.sqllogic +147 -0
  340. package/test/logic/51-materialized-views.sqllogic +376 -0
  341. package/test/logic/51.5-materialized-views-coarsened-key.sqllogic +198 -0
  342. package/test/logic/51.7-maintained-table-attach-detach.sqllogic +468 -0
  343. package/test/logic/51.8-maintained-table-declared-constraints.sqllogic +440 -0
  344. package/test/logic/51.85-maintained-table-parent-fk.sqllogic +103 -0
  345. package/test/logic/51.9-maintained-table-secondary-unique.sqllogic +237 -0
  346. package/test/logic/51.9.1-maintained-table-partial-unique.sqllogic +36 -0
  347. package/test/logic/52-lens-overrides.sqllogic +566 -0
  348. package/test/logic/53-materialized-views-rowtime.sqllogic +1601 -0
  349. package/test/logic/53-reserved-tags.sqllogic +252 -0
  350. package/test/logic/53.1-materialized-view-write-through.sqllogic +329 -0
  351. package/test/logic/53.2-materialized-view-rename-propagation.sqllogic +483 -0
  352. package/test/logic/53.3-materialized-view-constraint-only-ddl.sqllogic +409 -0
  353. package/test/logic/53.3.1-materialized-view-index-ddl-irrelevance.sqllogic +29 -0
  354. package/test/logic/53.4-materialized-view-structural-alter-restore.sqllogic +436 -0
  355. package/test/logic/54-covering-mv-enforcement.sqllogic +314 -0
  356. package/test/logic/54.1-covering-mv-partial-enforcement.sqllogic +34 -0
  357. package/test/logic/55-internal-eviction-reporting.sqllogic +89 -0
  358. package/test/logic/55-lens-prover.sqllogic +202 -0
  359. package/test/logic/55.1-eviction-restrict-fk.sqllogic +148 -0
  360. package/test/logic/55.5-lens-authored-inverse.sqllogic +1468 -0
  361. package/test/logic/80-grow-retrieve-pass.sqllogic +79 -0
  362. package/test/logic/81-quickpick.sqllogic +24 -0
  363. package/test/logic/82-bloom-join.sqllogic +185 -0
  364. package/test/logic/83-merge-join.sqllogic +280 -0
  365. package/test/logic/84-asof-scan.sqllogic +238 -0
  366. package/test/logic/84-key-cardinality.sqllogic +50 -0
  367. package/test/logic/85-relational-const-folding.sqllogic +51 -0
  368. package/test/logic/86-scalar-cse.sqllogic +42 -0
  369. package/test/logic/90-error_paths.sqllogic +114 -0
  370. package/test/logic/90.1-parse-errors.sqllogic +77 -0
  371. package/test/logic/90.2-alter-table-errors.sqllogic +53 -0
  372. package/test/logic/90.2.1-alter-extra-errors.sqllogic +51 -0
  373. package/test/logic/90.3-expression-errors.sqllogic +43 -0
  374. package/test/logic/90.4-dml-errors.sqllogic +47 -0
  375. package/test/logic/90.5-unsupported-join-types.sqllogic +37 -0
  376. package/test/logic/90.5.1-right-full-join-read.sqllogic +157 -0
  377. package/test/logic/90.6-select-error-paths.sqllogic +57 -0
  378. package/test/logic/91-merge-join-edge-cases.sqllogic +159 -0
  379. package/test/logic/92-hash-aggregate-edge-cases.sqllogic +139 -0
  380. package/test/logic/93-ddl-view-edge-cases.sqllogic +104 -0
  381. package/test/logic/93.1-view-error-paths.sqllogic +74 -0
  382. package/test/logic/93.2-view-mutation-pending.sqllogic +108 -0
  383. package/test/logic/93.3-view-mutation-or-clause.sqllogic +60 -0
  384. package/test/logic/93.4-view-mutation.sqllogic +4881 -0
  385. package/test/logic/93.4.1-view-mutation-partial-unique-cardinality.sqllogic +31 -0
  386. package/test/logic/93.5-authored-inverse.sqllogic +415 -0
  387. package/test/logic/93.6-set-op-flagless-write.sqllogic +862 -0
  388. package/test/logic/94-tvf-edge-cases.sqllogic +58 -0
  389. package/test/logic/94.1-limit-offset-edge-cases.sqllogic +109 -0
  390. package/test/logic/94.2-limit-offset-extras.sqllogic +98 -0
  391. package/test/logic/95-assertions.sqllogic +1417 -0
  392. package/test/logic/96-subquery-edge-cases.sqllogic +276 -0
  393. package/test/logic/97-json-function-edge-cases.sqllogic +633 -0
  394. package/test/logic/97.1-json-blob-and-special-values.sqllogic +92 -0
  395. package/test/logic/98-temporal-edge-cases.sqllogic +515 -0
  396. package/test/logic/99-conversion-edge-cases.sqllogic +325 -0
  397. package/test/logic/99.0.1-schema-index-listing.sqllogic +21 -0
  398. package/test/logic/99.1-cast-syntax-extras.sqllogic +167 -0
  399. package/test/logic/drop-unique-index.sqllogic +88 -0
  400. 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