@quereus/quereus 4.20.1 → 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 (377) hide show
  1. package/README.md +2 -0
  2. package/package.json +7 -4
  3. package/test/README.md +173 -0
  4. package/test/logic/01-basic.sqllogic +39 -0
  5. package/test/logic/01.1-select-projection-extras.sqllogic +130 -0
  6. package/test/logic/01.2-strings.sqllogic +43 -0
  7. package/test/logic/01.5-insert-select.sqllogic +237 -0
  8. package/test/logic/01.6-update-extras.sqllogic +118 -0
  9. package/test/logic/01.7-update-from.sqllogic +67 -0
  10. package/test/logic/01.8-delete-extras.sqllogic +122 -0
  11. package/test/logic/01.8.1-delete-range-cow.sqllogic +115 -0
  12. package/test/logic/01.9-query-expr-dml-gates.sqllogic +32 -0
  13. package/test/logic/01.9-query-expr-dml.sqllogic +171 -0
  14. package/test/logic/01.9-query-expr-values.sqllogic +111 -0
  15. package/test/logic/01.9.1-limit-over-dml-subquery.sqllogic +119 -0
  16. package/test/logic/02-filters.sqllogic +54 -0
  17. package/test/logic/02-smoke.sqllogic +62 -0
  18. package/test/logic/02.1-bind-parameters.sqllogic +223 -0
  19. package/test/logic/03-expressions.sqllogic +190 -0
  20. package/test/logic/03.1-quoted-identifiers.sqllogic +55 -0
  21. package/test/logic/03.4-defaults.sqllogic +554 -0
  22. package/test/logic/03.4.1-default-edge-cases.sqllogic +99 -0
  23. package/test/logic/03.5-tvf.sqllogic +61 -0
  24. package/test/logic/03.6-type-system.sqllogic +544 -0
  25. package/test/logic/03.6.1-numeric-text-comparison-coercion.sqllogic +244 -0
  26. package/test/logic/03.6.2-value-to-text.sqllogic +224 -0
  27. package/test/logic/03.7-bigint-mixed-arithmetic.sqllogic +65 -0
  28. package/test/logic/03.8-not-precedence.sqllogic +255 -0
  29. package/test/logic/03.9-is-bool-predicate.sqllogic +150 -0
  30. package/test/logic/03.9.1-is-bool-predicate-partial-index.sqllogic +34 -0
  31. package/test/logic/04-order-by.sqllogic +38 -0
  32. package/test/logic/04-transactions.sqllogic +239 -0
  33. package/test/logic/04a-savepoint-lazy-attach.sqllogic +122 -0
  34. package/test/logic/05-vtab_memory.sqllogic +50 -0
  35. package/test/logic/05.0.1-vtab-memory-unique-index-collation.sqllogic +34 -0
  36. package/test/logic/05.1-composite-pk-range-scan.sqllogic +147 -0
  37. package/test/logic/05.1.1-secondary-index-range-scan.sqllogic +56 -0
  38. package/test/logic/05.2-cast-seek-correctness.sqllogic +124 -0
  39. package/test/logic/06-builtin_functions.sqllogic +149 -0
  40. package/test/logic/06.1-string-functions.sqllogic +92 -0
  41. package/test/logic/06.1.3-like-glob-edges.sqllogic +53 -0
  42. package/test/logic/06.2-math-functions.sqllogic +82 -0
  43. package/test/logic/06.3-schema.sqllogic +48 -0
  44. package/test/logic/06.3.1-schema-all-schemas.sqllogic +39 -0
  45. package/test/logic/06.3.2-schema-foreign-keys.sqllogic +208 -0
  46. package/test/logic/06.3.3-introspection-tags.sqllogic +202 -0
  47. package/test/logic/06.3.3.1-introspection-tags-index.sqllogic +74 -0
  48. package/test/logic/06.3.4-view-info.sqllogic +378 -0
  49. package/test/logic/06.3.5-column-info.sqllogic +347 -0
  50. package/test/logic/06.3.6-schema-views.sqllogic +29 -0
  51. package/test/logic/06.4-schema-search-path.sqllogic +354 -0
  52. package/test/logic/06.4.1-schema-case-insensitive.sqllogic +83 -0
  53. package/test/logic/06.4.2-collation-extras.sqllogic +472 -0
  54. package/test/logic/06.4.2.1-collation-index-access-paths.sqllogic +140 -0
  55. package/test/logic/06.4.3-write-path-collation.sqllogic +99 -0
  56. package/test/logic/06.4.4-comparison-collation-precedence.sqllogic +342 -0
  57. package/test/logic/06.4.5-any-collate-declared-keys.sqllogic +92 -0
  58. package/test/logic/06.5-polymorphic-types.sqllogic +169 -0
  59. package/test/logic/06.5.1-conversion-functions.sqllogic +173 -0
  60. package/test/logic/06.5.2-scalar-minmax.sqllogic +16 -0
  61. package/test/logic/06.5.3-undeclared-return-type-comparison.sqllogic +244 -0
  62. package/test/logic/06.5.4-declared-return-type-builtins.sqllogic +288 -0
  63. package/test/logic/06.6-aggregate-extended.sqllogic +78 -0
  64. package/test/logic/06.7-json-extended.sqllogic +53 -0
  65. package/test/logic/06.8-json-path-operators.sqllogic +99 -0
  66. package/test/logic/06.9-json-canonical-key.sqllogic +65 -0
  67. package/test/logic/06.9.1-json-coerce-once.sqllogic +417 -0
  68. package/test/logic/06.9.1.1-json-coerce-once-index.sqllogic +39 -0
  69. package/test/logic/06.9.2-json-structural-equality.sqllogic +382 -0
  70. package/test/logic/06.9.2.1-json-structural-equality-index.sqllogic +76 -0
  71. package/test/logic/06.9.3-json-index-range-seek.sqllogic +205 -0
  72. package/test/logic/07-aggregates.sqllogic +193 -0
  73. package/test/logic/07.1-aggregate-filter-clause.sqllogic +12 -0
  74. package/test/logic/07.2-aggregate-order-by.sqllogic +11 -0
  75. package/test/logic/07.3-group-by-extras.sqllogic +602 -0
  76. package/test/logic/07.3.1-group-by-order-by-key.sqllogic +164 -0
  77. package/test/logic/07.3.2-grouped-select-list-shape.sqllogic +231 -0
  78. package/test/logic/07.4-group-by-filter-composite-pk.sqllogic +120 -0
  79. package/test/logic/07.5-sum-mixed-exact-and-float.sqllogic +151 -0
  80. package/test/logic/07.5-window.sqllogic +1941 -0
  81. package/test/logic/07.5.1-window-named.sqllogic +13 -0
  82. package/test/logic/07.5.2-window-nth-value.sqllogic +13 -0
  83. package/test/logic/07.6-subqueries.sqllogic +78 -0
  84. package/test/logic/07.6.1-subquery-extras.sqllogic +93 -0
  85. package/test/logic/07.7-and-or-short-circuit.sqllogic +58 -0
  86. package/test/logic/07.7-in-subquery-caching.sqllogic +174 -0
  87. package/test/logic/07.7-scalar-agg-decorrelation.sqllogic +197 -0
  88. package/test/logic/07.7.1-in-extras.sqllogic +158 -0
  89. package/test/logic/07.7.1-scalar-agg-decorrelation-filter.sqllogic +105 -0
  90. package/test/logic/07.7.2-scalar-agg-decorrelation-nested.sqllogic +115 -0
  91. package/test/logic/07.7.3-exists-in-select-decorrelation.sqllogic +107 -0
  92. package/test/logic/07.7.4-where-conjunct-ordering.sqllogic +121 -0
  93. package/test/logic/07.7.5-filter-lost-under-index-order.sqllogic +134 -0
  94. package/test/logic/07.7.6-correlated-predicate-scope.sqllogic +178 -0
  95. package/test/logic/07.7.7-join-source-scope-shadowing.sqllogic +137 -0
  96. package/test/logic/07.7.8-correlated-ref-to-computed-column.sqllogic +165 -0
  97. package/test/logic/07.7.9-conjunct-monotonicity.sqllogic +200 -0
  98. package/test/logic/07.8-correlated-subquery-edges.sqllogic +71 -0
  99. package/test/logic/07.8.1-correlated-in-decorrelation-gates.sqllogic +156 -0
  100. package/test/logic/07.9-in-value-list.sqllogic +154 -0
  101. package/test/logic/07.9.1-in-value-list-composite-index.sqllogic +38 -0
  102. package/test/logic/08-views.sqllogic +64 -0
  103. package/test/logic/08.1-semi-anti-join.sqllogic +191 -0
  104. package/test/logic/08.1-view-edge-cases.sqllogic +118 -0
  105. package/test/logic/08.1.1-uncorrelated-in-semijoin.sqllogic +251 -0
  106. package/test/logic/08.2-existence-flag-semijoin-recovery.sqllogic +197 -0
  107. package/test/logic/08.3-existence-flag-inner-recovery.sqllogic +102 -0
  108. package/test/logic/08.4-key-set-semi-join.sqllogic +447 -0
  109. package/test/logic/09-set_operations.sqllogic +244 -0
  110. package/test/logic/09.1-set-op-cross-collation.sqllogic +230 -0
  111. package/test/logic/10-distinct_datatypes.sqllogic +127 -0
  112. package/test/logic/10.1-ddl-lifecycle.sqllogic +203 -0
  113. package/test/logic/10.1.1-create-table-syntax-edges.sqllogic +47 -0
  114. package/test/logic/10.1.2-ddl-in-transaction.sqllogic +143 -0
  115. package/test/logic/10.1.2.1-add-constraint-in-transaction.sqllogic +59 -0
  116. package/test/logic/10.1.3-ddl-drop-in-transaction.sqllogic +187 -0
  117. package/test/logic/10.1.3.1-drop-constraint-in-transaction.sqllogic +45 -0
  118. package/test/logic/10.1.4-create-table-duplicate-constraint-names.sqllogic +149 -0
  119. package/test/logic/10.1.4-ddl-transaction-policy.sqllogic +314 -0
  120. package/test/logic/10.1.5-alter-add-constraint-minted-name-disambiguation.sqllogic +219 -0
  121. package/test/logic/10.1.5-create-index-lifecycle.sqllogic +72 -0
  122. package/test/logic/10.1.6-ddl-transaction-policy-index.sqllogic +143 -0
  123. package/test/logic/10.1.7-create-table-default-type-check.sqllogic +119 -0
  124. package/test/logic/10.1.8-alter-add-check-existing-rows.sqllogic +117 -0
  125. package/test/logic/10.2-column-features.sqllogic +365 -0
  126. package/test/logic/10.2.1-table-options-rejection.sqllogic +63 -0
  127. package/test/logic/10.2.2-default-collation-memory.sqllogic +30 -0
  128. package/test/logic/10.3-function-features.sqllogic +247 -0
  129. package/test/logic/10.4-schema-scale.sqllogic +168 -0
  130. package/test/logic/10.4.1-schema-scale-indexes.sqllogic +121 -0
  131. package/test/logic/10.5-indexes.sqllogic +164 -0
  132. package/test/logic/10.5.1-partial-indexes.sqllogic +526 -0
  133. package/test/logic/10.5.2-expression-indexes.sqllogic +34 -0
  134. package/test/logic/10.5.3-desc-index-ordering.sqllogic +130 -0
  135. package/test/logic/10.5.4-composite-pk-index-update-phantom.sqllogic +55 -0
  136. package/test/logic/10.5.5-index-name-uniqueness.sqllogic +76 -0
  137. package/test/logic/10.5.6-minmax-index-boundary.sqllogic +204 -0
  138. package/test/logic/10.5.7-implicit-unique-index-lifecycle.sqllogic +1216 -0
  139. package/test/logic/10.6-distinct-edge-cases.sqllogic +119 -0
  140. package/test/logic/100-predicate-normalization-edge-cases.sqllogic +238 -0
  141. package/test/logic/100.1-where-extras.sqllogic +163 -0
  142. package/test/logic/100.1.1-where-join-on-order-indexed.sqllogic +35 -0
  143. package/test/logic/101-builtin-mutation-kills.sqllogic +974 -0
  144. package/test/logic/101-transaction-edge-cases.sqllogic +395 -0
  145. package/test/logic/102-schema-catalog-edge-cases.sqllogic +252 -0
  146. package/test/logic/102-unique-constraints.sqllogic +145 -0
  147. package/test/logic/102.0.1-schema-catalog-indexed-table.sqllogic +32 -0
  148. package/test/logic/102.1-unique-edge-cases.sqllogic +111 -0
  149. package/test/logic/102.1.1-unique-index-creation-edge-cases.sqllogic +75 -0
  150. package/test/logic/102.2-unique-collation.sqllogic +181 -0
  151. package/test/logic/102.2.1-unique-collation-index-derived.sqllogic +305 -0
  152. package/test/logic/103-database-options-edge-cases.sqllogic +108 -0
  153. package/test/logic/103.1-pragma-boolean-values.sqllogic +126 -0
  154. package/test/logic/104-emit-mutation-kills.sqllogic +236 -0
  155. package/test/logic/105-vtab-memory-mutation-kills.sqllogic +754 -0
  156. package/test/logic/105.1-vtab-memory-index-mutation-kills.sqllogic +540 -0
  157. package/test/logic/106-constraint-extractor-mutation-kills.sqllogic +350 -0
  158. package/test/logic/107-temporal-arithmetic-mutation-kills.sqllogic +833 -0
  159. package/test/logic/108-cardinality-estimation.sqllogic +146 -0
  160. package/test/logic/108.1-analyze-name-resolution.sqllogic +102 -0
  161. package/test/logic/109-aggregate-physical-selection.sqllogic +103 -0
  162. package/test/logic/11-joins.sqllogic +86 -0
  163. package/test/logic/11.1-join-using.sqllogic +267 -0
  164. package/test/logic/11.2-comma-join.sqllogic +35 -0
  165. package/test/logic/11.3-index-nested-loop-join.sqllogic +305 -0
  166. package/test/logic/11.3-mismatched-collation-join.sqllogic +74 -0
  167. package/test/logic/11.4-hash-join-side-swap.sqllogic +118 -0
  168. package/test/logic/110-scan-emitter-mutation-kills.sqllogic +121 -0
  169. package/test/logic/110.1-scan-emitter-index-scan-kills.sqllogic +47 -0
  170. package/test/logic/12-empty-primary-key.sqllogic +124 -0
  171. package/test/logic/12-join_padding_order.sqllogic +58 -0
  172. package/test/logic/12.1-orderby-index-consumption.sqllogic +40 -0
  173. package/test/logic/13-cte.sqllogic +137 -0
  174. package/test/logic/13.1-cte-multiple-recursive.sqllogic +104 -0
  175. package/test/logic/13.10-cte-qualified-name.sqllogic +136 -0
  176. package/test/logic/13.11-unreferenced-dml-cte.sqllogic +414 -0
  177. package/test/logic/13.12-nested-dml-cte-rejected.sqllogic +280 -0
  178. package/test/logic/13.2-cte-bind-params.sqllogic +98 -0
  179. package/test/logic/13.3-cte-edge-cases.sqllogic +120 -0
  180. package/test/logic/13.4-cte-extras.sqllogic +98 -0
  181. package/test/logic/13.5-cte-join-order.sqllogic +166 -0
  182. package/test/logic/13.6-cte-dml-runs-once.sqllogic +318 -0
  183. package/test/logic/13.7-cte-sibling-visibility.sqllogic +227 -0
  184. package/test/logic/13.8-insert-with-clause-visibility.sqllogic +254 -0
  185. package/test/logic/13.9-schema-authored-cte-isolation.sqllogic +328 -0
  186. package/test/logic/13.9.1-schema-authored-schema-path-isolation.sqllogic +408 -0
  187. package/test/logic/14-utilities.sqllogic +168 -0
  188. package/test/logic/15-timespan.sqllogic +279 -0
  189. package/test/logic/15.1-semantic-ordering.sqllogic +960 -0
  190. package/test/logic/15.1.1-json-check-coercion.sqllogic +178 -0
  191. package/test/logic/15.1.2-semantic-ordering-index.sqllogic +41 -0
  192. package/test/logic/15.1.3-declared-form-write-contract.sqllogic +519 -0
  193. package/test/logic/16-epoch.sqllogic +187 -0
  194. package/test/logic/17-weekday-modifier.sqllogic +78 -0
  195. package/test/logic/18-json-string-escapes.sqllogic +86 -0
  196. package/test/logic/18-strftime-extended-formats.sqllogic +68 -0
  197. package/test/logic/20-empty-single-row.sqllogic +261 -0
  198. package/test/logic/21-null-edge-cases.sqllogic +227 -0
  199. package/test/logic/21.1-where-null-comparisons.sqllogic +151 -0
  200. package/test/logic/21.1.1-where-null-comparisons-secondary-index.sqllogic +35 -0
  201. package/test/logic/21.2-between-edges.sqllogic +157 -0
  202. package/test/logic/22-boundary-values.sqllogic +175 -0
  203. package/test/logic/23-self-joins-duplicates.sqllogic +74 -0
  204. package/test/logic/24-builtin-branches.sqllogic +611 -0
  205. package/test/logic/24.1-substr-extras.sqllogic +75 -0
  206. package/test/logic/24.2-random-extras.sqllogic +39 -0
  207. package/test/logic/25-aggregate-edge-cases.sqllogic +289 -0
  208. package/test/logic/25.1-nested-aggregates.sqllogic +74 -0
  209. package/test/logic/25.2-having-edge-cases.sqllogic +318 -0
  210. package/test/logic/25.3-aggregate-isnull-empty.sqllogic +16 -0
  211. package/test/logic/25.4-groupby-fd-constant-empty.sqllogic +55 -0
  212. package/test/logic/25.5-ungrouped-aggregate-column-free-select-item.sqllogic +196 -0
  213. package/test/logic/26-join-edge-cases.sqllogic +333 -0
  214. package/test/logic/26.1-left-join-isnull-on-notnull.sqllogic +26 -0
  215. package/test/logic/26.2-left-join-on-vs-where.sqllogic +67 -0
  216. package/test/logic/26.3-join-on-constants.sqllogic +61 -0
  217. package/test/logic/26.4-join-on-is-null-nullable.sqllogic +44 -0
  218. package/test/logic/27-window-edge-cases.sqllogic +206 -0
  219. package/test/logic/27.1-window-groups-frame.sqllogic +13 -0
  220. package/test/logic/27.2-window-exclude.sqllogic +13 -0
  221. package/test/logic/27.3-window-json-aggregation.sqllogic +66 -0
  222. package/test/logic/28-set-ops-sort-edge-cases.sqllogic +444 -0
  223. package/test/logic/28.1-compound-limit-offset.sqllogic +143 -0
  224. package/test/logic/28.2-orderby-expression-extras.sqllogic +698 -0
  225. package/test/logic/28.2-set-op-branch-types.sqllogic +217 -0
  226. package/test/logic/29-constraint-edge-cases.sqllogic +442 -0
  227. package/test/logic/29.1-column-level-conflict-clause.sqllogic +171 -0
  228. package/test/logic/29.2-table-level-pk-conflict-clause.sqllogic +113 -0
  229. package/test/logic/40-constraints.sqllogic +418 -0
  230. package/test/logic/40.1-pk-desc-direction.sqllogic +76 -0
  231. package/test/logic/40.2-check-extras.sqllogic +683 -0
  232. package/test/logic/41-alter-table.sqllogic +191 -0
  233. package/test/logic/41-fk-cascade-conflict-and-self-ref.sqllogic +263 -0
  234. package/test/logic/41-fk-cross-schema.sqllogic +155 -0
  235. package/test/logic/41-fk-extended-targets.sqllogic +173 -0
  236. package/test/logic/41-foreign-keys.sqllogic +1001 -0
  237. package/test/logic/41-generated-column-errors.sqllogic +225 -0
  238. package/test/logic/41-generated-column-extras.sqllogic +598 -0
  239. package/test/logic/41-generated-column-scope.sqllogic +329 -0
  240. package/test/logic/41-generated-columns.sqllogic +361 -0
  241. package/test/logic/41.1-alter-pk.sqllogic +230 -0
  242. package/test/logic/41.1-fk-collation-conflict.sqllogic +277 -0
  243. package/test/logic/41.10-alter-drop-column-foreign-key.sqllogic +274 -0
  244. package/test/logic/41.10.1-alter-drop-column-unique-index.sqllogic +169 -0
  245. package/test/logic/41.10.2-alter-drop-column-check-and-assertion.sqllogic +1616 -0
  246. package/test/logic/41.10.3-alter-drop-column-referencing-fk.sqllogic +292 -0
  247. package/test/logic/41.10.4-drop-object-expression-dependents.sqllogic +602 -0
  248. package/test/logic/41.11-deferred-fk-with-rename.sqllogic +232 -0
  249. package/test/logic/41.12-alter-default-coercion.sqllogic +200 -0
  250. package/test/logic/41.13-alter-add-column-generated-backfill.sqllogic +566 -0
  251. package/test/logic/41.14-alter-add-column-subquery-backfill.sqllogic +129 -0
  252. package/test/logic/41.15-alter-column-default-type-check.sqllogic +178 -0
  253. package/test/logic/41.16-fk-unenforceable.sqllogic +364 -0
  254. package/test/logic/41.2-alter-column.sqllogic +382 -0
  255. package/test/logic/41.2.1-alter-column-retype-deleted-row-memory.sqllogic +32 -0
  256. package/test/logic/41.2.2-alter-column-retype-index-rebuild.sqllogic +77 -0
  257. package/test/logic/41.2.3-alter-column-set-not-null-pk-backfill.sqllogic +310 -0
  258. package/test/logic/41.2.3.1-alter-column-set-not-null-pk-backfill-secondary-index.sqllogic +37 -0
  259. package/test/logic/41.3-alter-add-column-unique.sqllogic +184 -0
  260. package/test/logic/41.3-alter-rename-propagation.sqllogic +2352 -0
  261. package/test/logic/41.3.1-alter-rename-index-propagation.sqllogic +94 -0
  262. package/test/logic/41.4-alter-add-column-constraints.sqllogic +855 -0
  263. package/test/logic/41.5-alter-misc.sqllogic +48 -0
  264. package/test/logic/41.5-cross-schema-foreign-keys.sqllogic +253 -0
  265. package/test/logic/41.6-alter-drop-rename-constraint.sqllogic +354 -0
  266. package/test/logic/41.6.1-alter-drop-constraint-index-derived.sqllogic +22 -0
  267. package/test/logic/41.7-alter-column-collate.sqllogic +105 -0
  268. package/test/logic/41.7.1-alter-column-collate-unique.sqllogic +45 -0
  269. package/test/logic/41.7.2-alter-column-collate-unique-store.sqllogic +208 -0
  270. package/test/logic/41.7.2.1-alter-column-collate-unique-index-store.sqllogic +114 -0
  271. package/test/logic/41.7.3-alter-column-retype-unique.sqllogic +293 -0
  272. package/test/logic/41.7.3.1-alter-column-retype-staged-rows-memory.sqllogic +41 -0
  273. package/test/logic/41.7.3.2-alter-column-retype-unique-index.sqllogic +72 -0
  274. package/test/logic/41.7.4-alter-column-retype-semantic-memory.sqllogic +445 -0
  275. package/test/logic/41.7.4.1-alter-column-retype-semantic-index-memory.sqllogic +91 -0
  276. package/test/logic/41.7.5-alter-column-collate-pk-staged-delete-memory.sqllogic +106 -0
  277. package/test/logic/41.8-alter-add-constraint-unique-fk.sqllogic +275 -0
  278. package/test/logic/41.8-alter-savepoint-staged-rows.sqllogic +443 -0
  279. package/test/logic/41.8.1-alter-rename-savepoint-staged-rows.sqllogic +408 -0
  280. package/test/logic/41.9-alter-constraint-body-change.sqllogic +180 -0
  281. package/test/logic/41.9-fk-restrict-batched.sqllogic +290 -0
  282. package/test/logic/42-committed-snapshot.sqllogic +254 -0
  283. package/test/logic/42-returning.sqllogic +159 -0
  284. package/test/logic/42.1-returning-extras.sqllogic +232 -0
  285. package/test/logic/42.2-returning-stored-row.sqllogic +165 -0
  286. package/test/logic/43-default-nullability.sqllogic +131 -0
  287. package/test/logic/43-transition-constraints.sqllogic +341 -0
  288. package/test/logic/43.1-default-collation.sqllogic +140 -0
  289. package/test/logic/43.1-notnull-or-conflict.sqllogic +79 -0
  290. package/test/logic/43.2-deferred-check-new-on-delete.sqllogic +95 -0
  291. package/test/logic/43.3-nullable-primary-key.sqllogic +587 -0
  292. package/test/logic/44-determinism-validation.sqllogic +164 -0
  293. package/test/logic/44-orthogonality.sqllogic +146 -0
  294. package/test/logic/44.1-nondeterministic-schema.sqllogic +316 -0
  295. package/test/logic/45-udf-determinism.sqllogic +39 -0
  296. package/test/logic/46-mutation-context.sqllogic +595 -0
  297. package/test/logic/47-upsert.sqllogic +255 -0
  298. package/test/logic/47.1-upsert-conflict-targets.sqllogic +169 -0
  299. package/test/logic/47.2-replace-and-or-clauses.sqllogic +164 -0
  300. package/test/logic/47.2.1-pk-replace-secondary-unique.sqllogic +374 -0
  301. package/test/logic/47.3-engine-or-fail.sqllogic +122 -0
  302. package/test/logic/47.3-upsert-conflict-target-collation.sqllogic +139 -0
  303. package/test/logic/47.3.1-upsert-conflict-index-derived-collation.sqllogic +25 -0
  304. package/test/logic/47.4-upsert-conflict-target-affinity.sqllogic +59 -0
  305. package/test/logic/47.5-upsert-do-update-constraints.sqllogic +440 -0
  306. package/test/logic/47.6-upsert-do-update-decomposed.sqllogic +100 -0
  307. package/test/logic/49-reference-graph.sqllogic +84 -0
  308. package/test/logic/50-declarative-schema.sqllogic +2550 -0
  309. package/test/logic/50-metadata-tags.sqllogic +1033 -0
  310. package/test/logic/50.0.1-metadata-tags-index.sqllogic +141 -0
  311. package/test/logic/50.1-declare-schema-pk.sqllogic +150 -0
  312. package/test/logic/50.2-declare-schema-renames.sqllogic +949 -0
  313. package/test/logic/50.2.1-declare-schema-index-renames.sqllogic +173 -0
  314. package/test/logic/50.3-declare-schema-duplicate-names.sqllogic +280 -0
  315. package/test/logic/50.4-declare-schema-apply-restore.sqllogic +192 -0
  316. package/test/logic/51-lens-foundation.sqllogic +147 -0
  317. package/test/logic/51-materialized-views.sqllogic +376 -0
  318. package/test/logic/51.5-materialized-views-coarsened-key.sqllogic +198 -0
  319. package/test/logic/51.7-maintained-table-attach-detach.sqllogic +468 -0
  320. package/test/logic/51.8-maintained-table-declared-constraints.sqllogic +440 -0
  321. package/test/logic/51.85-maintained-table-parent-fk.sqllogic +103 -0
  322. package/test/logic/51.9-maintained-table-secondary-unique.sqllogic +237 -0
  323. package/test/logic/51.9.1-maintained-table-partial-unique.sqllogic +36 -0
  324. package/test/logic/52-lens-overrides.sqllogic +566 -0
  325. package/test/logic/53-materialized-views-rowtime.sqllogic +1601 -0
  326. package/test/logic/53-reserved-tags.sqllogic +252 -0
  327. package/test/logic/53.1-materialized-view-write-through.sqllogic +329 -0
  328. package/test/logic/53.2-materialized-view-rename-propagation.sqllogic +483 -0
  329. package/test/logic/53.3-materialized-view-constraint-only-ddl.sqllogic +409 -0
  330. package/test/logic/53.3.1-materialized-view-index-ddl-irrelevance.sqllogic +29 -0
  331. package/test/logic/53.4-materialized-view-structural-alter-restore.sqllogic +436 -0
  332. package/test/logic/54-covering-mv-enforcement.sqllogic +314 -0
  333. package/test/logic/54.1-covering-mv-partial-enforcement.sqllogic +34 -0
  334. package/test/logic/55-internal-eviction-reporting.sqllogic +89 -0
  335. package/test/logic/55-lens-prover.sqllogic +202 -0
  336. package/test/logic/55.1-eviction-restrict-fk.sqllogic +148 -0
  337. package/test/logic/55.5-lens-authored-inverse.sqllogic +1468 -0
  338. package/test/logic/80-grow-retrieve-pass.sqllogic +79 -0
  339. package/test/logic/81-quickpick.sqllogic +24 -0
  340. package/test/logic/82-bloom-join.sqllogic +185 -0
  341. package/test/logic/83-merge-join.sqllogic +280 -0
  342. package/test/logic/84-asof-scan.sqllogic +238 -0
  343. package/test/logic/84-key-cardinality.sqllogic +50 -0
  344. package/test/logic/85-relational-const-folding.sqllogic +51 -0
  345. package/test/logic/86-scalar-cse.sqllogic +42 -0
  346. package/test/logic/90-error_paths.sqllogic +114 -0
  347. package/test/logic/90.1-parse-errors.sqllogic +77 -0
  348. package/test/logic/90.2-alter-table-errors.sqllogic +53 -0
  349. package/test/logic/90.2.1-alter-extra-errors.sqllogic +51 -0
  350. package/test/logic/90.3-expression-errors.sqllogic +43 -0
  351. package/test/logic/90.4-dml-errors.sqllogic +47 -0
  352. package/test/logic/90.5-unsupported-join-types.sqllogic +37 -0
  353. package/test/logic/90.5.1-right-full-join-read.sqllogic +157 -0
  354. package/test/logic/90.6-select-error-paths.sqllogic +57 -0
  355. package/test/logic/91-merge-join-edge-cases.sqllogic +159 -0
  356. package/test/logic/92-hash-aggregate-edge-cases.sqllogic +139 -0
  357. package/test/logic/93-ddl-view-edge-cases.sqllogic +104 -0
  358. package/test/logic/93.1-view-error-paths.sqllogic +74 -0
  359. package/test/logic/93.2-view-mutation-pending.sqllogic +108 -0
  360. package/test/logic/93.3-view-mutation-or-clause.sqllogic +60 -0
  361. package/test/logic/93.4-view-mutation.sqllogic +4881 -0
  362. package/test/logic/93.4.1-view-mutation-partial-unique-cardinality.sqllogic +31 -0
  363. package/test/logic/93.5-authored-inverse.sqllogic +415 -0
  364. package/test/logic/93.6-set-op-flagless-write.sqllogic +862 -0
  365. package/test/logic/94-tvf-edge-cases.sqllogic +58 -0
  366. package/test/logic/94.1-limit-offset-edge-cases.sqllogic +109 -0
  367. package/test/logic/94.2-limit-offset-extras.sqllogic +98 -0
  368. package/test/logic/95-assertions.sqllogic +1417 -0
  369. package/test/logic/96-subquery-edge-cases.sqllogic +276 -0
  370. package/test/logic/97-json-function-edge-cases.sqllogic +633 -0
  371. package/test/logic/97.1-json-blob-and-special-values.sqllogic +92 -0
  372. package/test/logic/98-temporal-edge-cases.sqllogic +515 -0
  373. package/test/logic/99-conversion-edge-cases.sqllogic +325 -0
  374. package/test/logic/99.0.1-schema-index-listing.sqllogic +21 -0
  375. package/test/logic/99.1-cast-syntax-extras.sqllogic +167 -0
  376. package/test/logic/drop-unique-index.sqllogic +88 -0
  377. package/test/logic/pushdown-test.sqllogic +36 -0
@@ -0,0 +1,1216 @@
1
+ -- 10.5.7-implicit-unique-index-lifecycle.sqllogic
2
+ --
3
+ -- requires-capability: standalone-index-ddl
4
+ --
5
+ -- The implicit covering structure a declared UNIQUE constraint builds for
6
+ -- enforcement is named after that constraint (or `_uc_<cols>` when unnamed), but
7
+ -- it is NOT a user-addressable index: its lifecycle belongs to the constraint.
8
+ -- `DROP INDEX <that name>` therefore raises `no such index` (IF EXISTS: no-op)
9
+ -- and `CREATE INDEX <that name>` on the same table is refused as a duplicate.
10
+ -- Removing the structure means dropping the constraint.
11
+ --
12
+ -- Runs on BOTH backends deliberately: memory materializes the structure as a
13
+ -- real index entry, the store does not, and the guard reads `uniqueConstraints`
14
+ -- (present on both) so the observable behavior must match.
15
+
16
+ -- ============================================================
17
+ -- 1. Named UNIQUE constraint — DROP INDEX refused, enforcement intact
18
+ -- ============================================================
19
+ create table iul_a (id integer primary key, email text, constraint uq_email unique (email));
20
+ insert into iul_a values (1, 'a@x');
21
+ -- run
22
+
23
+ drop index uq_email;
24
+ -- error: no such index
25
+
26
+ drop index if exists uq_email;
27
+ -- run
28
+
29
+ -- Enforcement survives both attempts.
30
+ insert into iul_a values (2, 'a@x');
31
+ -- error: unique
32
+
33
+ select count(*) as c from iul_a;
34
+ → [{"c":1}]
35
+
36
+ -- The constraint is still registered.
37
+ select count(*) as c from unique_constraint_info('iul_a') where name = 'uq_email';
38
+ → [{"c":1}]
39
+
40
+ -- ============================================================
41
+ -- 2. Named UNIQUE constraint — CREATE INDEX of the same name on the same table
42
+ -- ============================================================
43
+ create index uq_email on iul_a (email);
44
+ -- error: already exists on table iul_a
45
+
46
+ -- IF NOT EXISTS skips silently rather than erroring...
47
+ create index if not exists uq_email on iul_a (email);
48
+ -- run
49
+
50
+ -- ...and adds nothing: the name is still only the constraint's backing structure.
51
+ create index uq_email on iul_a (email);
52
+ -- error: already exists on table iul_a
53
+
54
+ -- ============================================================
55
+ -- 3. Unnamed UNIQUE constraint — same rules under the auto-name `_uc_<cols>`
56
+ -- ============================================================
57
+ create table iul_u (id integer primary key, email text, unique (email));
58
+ insert into iul_u values (1, 'u@x');
59
+ -- run
60
+
61
+ drop index _uc_email;
62
+ -- error: no such index
63
+
64
+ drop index if exists _uc_email;
65
+ -- run
66
+
67
+ create index _uc_email on iul_u (email);
68
+ -- error: already exists on table iul_u
69
+
70
+ insert into iul_u values (2, 'u@x');
71
+ -- error: unique
72
+
73
+ -- ============================================================
74
+ -- 4. EXPOSED implicit structure — addressable for TAGS, still not for lifecycle
75
+ -- ============================================================
76
+ create table iul_e (id integer primary key, vin text,
77
+ constraint uq_vin unique (vin) with tags ("quereus.expose_implicit_index" = true));
78
+ insert into iul_e values (1, 'VIN1');
79
+ -- run
80
+
81
+ -- Exposure makes it visible and tag-addressable...
82
+ alter index uq_vin add tags (purpose = 'lookup');
83
+ select json_extract(tags, '$.purpose') as purpose from schema() where type = 'index' and name = 'uq_vin';
84
+ → [{"purpose":"lookup"}]
85
+
86
+ -- ...but its lifecycle still belongs to the constraint.
87
+ drop index uq_vin;
88
+ -- error: no such index
89
+
90
+ drop index if exists uq_vin;
91
+ -- run
92
+
93
+ create index uq_vin on iul_e (vin);
94
+ -- error: already exists on table iul_e
95
+
96
+ insert into iul_e values (2, 'VIN1');
97
+ -- error: unique
98
+
99
+ -- Still exposed after the refused drops.
100
+ select count(*) as c from schema() where type = 'index' and name = 'uq_vin';
101
+ → [{"c":1}]
102
+
103
+ -- It describes itself as UNIQUE, on every backend and on every read surface.
104
+ -- Rendered DDL exists to re-parse into the object it describes, and a plain
105
+ -- `CREATE INDEX` does not re-parse into something that enforces uniqueness.
106
+ -- Memory materializes the structure without a `unique` flag and the store
107
+ -- synthesizes it from the constraint, so both routes go through one enumeration
108
+ -- (`displayIndexesForTable`, which renders via `indexSchemaForDisplay`) rather
109
+ -- than each read surface deciding for itself.
110
+ -- Uniqueness *enforcement* stays the constraint's either way.
111
+ select sql like 'CREATE UNIQUE INDEX%' as is_unique_ddl from schema() where type = 'index' and name = 'uq_vin';
112
+ → [{"is_unique_ddl":true}]
113
+
114
+ select "unique" as u from index_info('iul_e') where index_name = 'uq_vin';
115
+ → [{"u":1}]
116
+
117
+ -- Negative pin: the helper must not over-apply. A PLAIN (unexposed) UNIQUE
118
+ -- constraint still surfaces no index row at all...
119
+ create table iul_p (id integer primary key, sku text, constraint uq_sku unique (sku));
120
+ -- run
121
+
122
+ select count(*) as c from schema() where type = 'index' and name = 'uq_sku';
123
+ → [{"c":0}]
124
+
125
+ select count(*) as c from index_info('iul_p');
126
+ → [{"c":0}]
127
+
128
+ -- ...and an ordinary non-unique index still renders without UNIQUE and reports 0.
129
+ create table iul_o (id integer primary key, label text);
130
+ create index ix_label on iul_o (label);
131
+ -- run
132
+
133
+ select sql like 'CREATE INDEX%' as is_plain_ddl from schema() where type = 'index' and name = 'ix_label';
134
+ → [{"is_plain_ddl":true}]
135
+
136
+ select "unique" as u from index_info('iul_o') where index_name = 'ix_label';
137
+ → [{"u":0}]
138
+
139
+ -- Shapes the named single-column pin above does not reach. Each is exposed, so
140
+ -- each must describe itself as UNIQUE on both backends.
141
+ -- (a) An UNNAMED constraint: the structure takes the auto-name `_uc_<cols>`.
142
+ create table iul_n (id integer primary key, code text,
143
+ unique (code) with tags ("quereus.expose_implicit_index" = true));
144
+ -- run
145
+
146
+ select sql like 'CREATE UNIQUE INDEX%' as is_unique_ddl from schema() where type = 'index' and name = '_uc_code';
147
+ → [{"is_unique_ddl":true}]
148
+
149
+ select "unique" as u from index_info('iul_n') where index_name = '_uc_code';
150
+ → [{"u":1}]
151
+
152
+ -- (b) A MULTI-COLUMN constraint under a MIXED-CASE name. The exposure map is
153
+ -- lowercase-keyed, so an unfolded lookup would render this one without UNIQUE;
154
+ -- and every column of the structure must carry the same answer.
155
+ create table iul_m (id integer primary key, a integer not null, b integer not null,
156
+ constraint UQ_AB unique (a, b) with tags ("quereus.expose_implicit_index" = true));
157
+ -- run
158
+
159
+ select sql like 'CREATE UNIQUE INDEX%' as is_unique_ddl from schema() where type = 'index' and name = 'UQ_AB';
160
+ → [{"is_unique_ddl":true}]
161
+
162
+ select count(*) as c from index_info('iul_m') where index_name = 'UQ_AB' and "unique" = 1;
163
+ → [{"c":2}]
164
+
165
+ -- ============================================================
166
+ -- 5. Mixed-case constraint name — the guards fold case like every index compare
167
+ -- ============================================================
168
+ create table iul_z (id integer primary key, email text, constraint UQ_Email unique (email));
169
+ insert into iul_z values (1, 'z@x');
170
+ -- run
171
+
172
+ drop index UQ_Email;
173
+ -- error: no such index
174
+
175
+ drop index uq_email;
176
+ -- error: no such index
177
+
178
+ create index uq_email on iul_z (email);
179
+ -- error: already exists on table iul_z
180
+
181
+ insert into iul_z values (2, 'z@x');
182
+ -- error: unique
183
+
184
+ -- ============================================================
185
+ -- 6. An implicit name on ANOTHER table is a real, droppable index
186
+ -- ============================================================
187
+ -- Constraint names are unique per TABLE, so `uq_shared` may be a constraint on
188
+ -- iul_b and an ordinary index on iul_c at the same time. The owner scan must
189
+ -- skip past iul_b's implicit match and keep going, not stop at it.
190
+ create table iul_b (id integer primary key, email text, constraint uq_shared unique (email));
191
+ create table iul_c (id integer primary key, email text);
192
+ create index uq_shared on iul_c (email);
193
+ insert into iul_b values (1, 'b@x');
194
+ insert into iul_c values (1, 'c@x'), (2, 'c2@x');
195
+ -- run
196
+
197
+ select id from iul_c where email = 'c2@x';
198
+ → [{"id":2}]
199
+
200
+ -- Resolves to iul_c's real index, not iul_b's backing structure.
201
+ drop index uq_shared;
202
+ -- run
203
+
204
+ -- iul_b's constraint is untouched and still enforcing.
205
+ select count(*) as c from unique_constraint_info('iul_b') where name = 'uq_shared';
206
+ → [{"c":1}]
207
+
208
+ insert into iul_b values (2, 'b@x');
209
+ -- error: unique
210
+
211
+ -- iul_c's rows survive the index drop and are still readable by the column.
212
+ select id from iul_c where email = 'c2@x';
213
+ → [{"id":2}]
214
+
215
+ -- The name is free again on iul_c.
216
+ create index uq_shared on iul_c (email);
217
+ -- run
218
+
219
+ drop index uq_shared;
220
+ -- run
221
+
222
+ -- ============================================================
223
+ -- 6a. `schema()` / `index_info()` hide the backing structure on BOTH backends
224
+ -- ============================================================
225
+ -- Named constraint (iul_a's uq_email): absent from schema()...
226
+ select count(*) as c from schema() where type = 'index' and name = 'uq_email' and tbl_name = 'iul_a';
227
+ → [{"c":0}]
228
+
229
+ -- ...and from index_info().
230
+ select count(*) as c from index_info('iul_a') where index_name = 'uq_email';
231
+ → [{"c":0}]
232
+
233
+ -- Unnamed constraint's auto-name (iul_u's _uc_email): same.
234
+ select count(*) as c from schema() where type = 'index' and name = '_uc_email' and tbl_name = 'iul_u';
235
+ → [{"c":0}]
236
+
237
+ select count(*) as c from index_info('iul_u') where index_name = '_uc_email';
238
+ → [{"c":0}]
239
+
240
+ -- Mixed-case constraint name (iul_z's UQ_Email): the predicate folds case.
241
+ select count(*) as c from schema() where type = 'index' and lower(name) = 'uq_email' and tbl_name = 'iul_z';
242
+ → [{"c":0}]
243
+
244
+ select count(*) as c from index_info('iul_z') where lower(index_name) = 'uq_email';
245
+ → [{"c":0}]
246
+
247
+ -- The EXPOSED implicit structure (iul_e's uq_vin) keeps showing up, with tags,
248
+ -- via index_info() too — it already does via schema() per section 4 above.
249
+ select count(*) as c from index_info('iul_e') where index_name = 'uq_vin';
250
+ → [{"c":1}]
251
+
252
+ select json_extract(tags, '$.purpose') as purpose from index_info('iul_e') where index_name = 'uq_vin';
253
+ → [{"purpose":"lookup"}]
254
+
255
+ -- `create unique index` synthesizes a constraint for enforcement, but the index
256
+ -- is the user's own object (not an implicit covering structure) and must keep
257
+ -- showing up on both introspection surfaces.
258
+ create table iul_d (id integer primary key, email text);
259
+ insert into iul_d values (1, 'd@x');
260
+ create unique index uq_d_email on iul_d (email);
261
+ -- run
262
+
263
+ select count(*) as c from schema() where type = 'index' and name = 'uq_d_email';
264
+ → [{"c":1}]
265
+
266
+ select count(*) as c from index_info('iul_d') where index_name = 'uq_d_email';
267
+ → [{"c":1}]
268
+
269
+ drop table iul_d;
270
+ -- run
271
+
272
+ -- ============================================================
273
+ -- 7. DROP CONSTRAINT is the way to remove the structure
274
+ -- ============================================================
275
+ alter table iul_a drop constraint uq_email;
276
+ -- run
277
+
278
+ select count(*) as c from unique_constraint_info('iul_a') where name = 'uq_email';
279
+ → [{"c":0}]
280
+
281
+ -- Duplicate accepted once the constraint (and its backing structure) is gone.
282
+ insert into iul_a values (2, 'a@x');
283
+ select count(*) as c from iul_a;
284
+ → [{"c":2}]
285
+
286
+ -- ...and the name is now an ordinary one on that table.
287
+ create index uq_email on iul_a (email);
288
+ -- run
289
+
290
+ drop index uq_email;
291
+ -- run
292
+
293
+ -- ============================================================
294
+ -- 8. The strict-DDL-policy gate skips implicit structures the same way
295
+ -- ============================================================
296
+ -- `emitDropIndex` resolves the owning table before the `ddl_transaction_policy`
297
+ -- gate fires, so its scan must apply the same skip. A name held ONLY by a
298
+ -- constraint's structure has no droppable owner, so the statement fails with
299
+ -- `no such index` and never reaches the gate; a real index of that name on
300
+ -- another table IS found past the skip, so the gate does fire for it.
301
+
302
+ pragma ddl_transaction_policy = 'strict';
303
+ -- run
304
+
305
+ -- Allowed in autocommit; iul_b still carries the `uq_shared` constraint.
306
+ create index uq_shared on iul_c (email);
307
+ -- run
308
+
309
+ begin;
310
+ -- run
311
+ drop index uq_shared;
312
+ -- error: ddl_transaction_policy = strict
313
+ rollback;
314
+ -- run
315
+
316
+ begin;
317
+ -- run
318
+ drop index UQ_Email;
319
+ -- error: no such index
320
+ rollback;
321
+ -- run
322
+
323
+ pragma ddl_transaction_policy = 'permissive';
324
+ -- run
325
+
326
+ -- The gate refused rather than dropped: the index is still there.
327
+ drop index uq_shared;
328
+ -- run
329
+
330
+ drop table iul_c;
331
+ drop table iul_b;
332
+ drop table iul_z;
333
+ drop table iul_e;
334
+ drop table iul_u;
335
+ drop table iul_a;
336
+ -- run
337
+
338
+ -- ============================================================
339
+ -- 9. The same rule from the CONSTRAINT side — declaring second is refused too
340
+ -- ============================================================
341
+ -- Sections 2/3/5 cover `CREATE INDEX` onto a name a UNIQUE constraint already
342
+ -- holds. The mirror image — declaring a UNIQUE constraint onto a name an index on
343
+ -- the SAME table already holds — is refused identically, on every authoring path.
344
+ -- Left unguarded it silently reclassifies the user's index as a hidden backing
345
+ -- structure: it disappears from schema() / index_info(), stops being droppable,
346
+ -- and (store-backed) its CREATE INDEX line is dropped from the persisted catalog.
347
+
348
+ create table iul_ac (id integer primary key, a text, b text);
349
+ create index cs_named on iul_ac (b);
350
+ insert into iul_ac values (1, 'x', 'p'), (2, 'y', 'q');
351
+ -- run
352
+
353
+ -- 9a. ALTER TABLE ADD CONSTRAINT
354
+ alter table iul_ac add constraint cs_named unique (a);
355
+ -- error: would collide with existing index 'cs_named'
356
+
357
+ -- The index is untouched by the refusal: still visible, still resolving rows.
358
+ select count(*) as c from schema() where type = 'index' and name = 'cs_named';
359
+ → [{"c":1}]
360
+
361
+ select id from iul_ac where b = 'q';
362
+ → [{"id":2}]
363
+
364
+ -- No duplicate index entry was left behind either.
365
+ select count(*) as c from index_info('iul_ac') where index_name = 'cs_named';
366
+ → [{"c":1}]
367
+
368
+ -- Case folds, like every other index-name compare.
369
+ alter table iul_ac add constraint CS_Named unique (a);
370
+ -- error: would collide with existing index 'cs_named'
371
+
372
+ -- Rejected even when the index is over the SAME column, where the two structures
373
+ -- would physically coincide: accepting it would silently hide the user's index.
374
+ create index cs_same on iul_ac (a);
375
+ -- run
376
+
377
+ alter table iul_ac add constraint cs_same unique (a);
378
+ -- error: would collide with existing index 'cs_same'
379
+
380
+ -- A DIFFERENTLY-named constraint over an already-indexed column stays legal —
381
+ -- reuse matches on columns, not on names.
382
+ alter table iul_ac add constraint cs_other unique (a);
383
+ -- run
384
+
385
+ insert into iul_ac values (3, 'x', 'r');
386
+ -- error: unique
387
+
388
+ -- 9b. Unnamed UNIQUE — the auto-name `_uc_<cols>` is guarded the same way
389
+ create table iul_au (id integer primary key, a text);
390
+ create index _uc_a on iul_au (a);
391
+ -- run
392
+
393
+ alter table iul_au add unique (a);
394
+ -- error: would collide with existing index '_uc_a'
395
+
396
+ select count(*) as c from schema() where type = 'index' and name = '_uc_a';
397
+ → [{"c":1}]
398
+
399
+ -- Multi-column: the auto-name joins the declared column names with `_`.
400
+ create table iul_am (id integer primary key, a text, b text, c text);
401
+ create index _uc_a_b on iul_am (c);
402
+ -- run
403
+
404
+ alter table iul_am add unique (a, b);
405
+ -- error: would collide with existing index '_uc_a_b'
406
+
407
+ select count(*) as c from schema() where type = 'index' and name = '_uc_a_b';
408
+ → [{"c":1}]
409
+
410
+ -- A free multi-column auto-name still installs and enforces.
411
+ alter table iul_am add unique (b, c);
412
+ insert into iul_am values (1, 'p', 'q', 'r');
413
+ -- run
414
+
415
+ insert into iul_am values (2, 'z', 'q', 'r');
416
+ -- error: unique
417
+
418
+ -- 9c. ALTER TABLE RENAME CONSTRAINT
419
+ create table iul_rc (id integer primary key, a text, b text);
420
+ create index rc_taken on iul_rc (b);
421
+ alter table iul_rc add constraint rc_uq unique (a);
422
+ -- run
423
+
424
+ alter table iul_rc rename constraint rc_uq to rc_taken;
425
+ -- error: would collide with existing index 'rc_taken'
426
+
427
+ -- Neither object moved: the constraint keeps its name and the index keeps its own.
428
+ select count(*) as c from unique_constraint_info('iul_rc') where name = 'rc_uq';
429
+ → [{"c":1}]
430
+
431
+ select count(*) as c from schema() where type = 'index' and name = 'rc_taken';
432
+ → [{"c":1}]
433
+
434
+ -- A free name still renames, and a case-only rename is still a no-op (it must not
435
+ -- trip over the constraint's OWN backing structure).
436
+ alter table iul_rc rename constraint rc_uq to rc_moved;
437
+ -- run
438
+
439
+ alter table iul_rc rename constraint rc_moved to RC_Moved;
440
+ -- run
441
+
442
+ select count(*) as c from unique_constraint_info('iul_rc') where lower(name) = 'rc_moved';
443
+ → [{"c":1}]
444
+
445
+ -- 9d. ALTER TABLE ADD COLUMN … unique
446
+ create table iul_cc (id integer primary key, a text);
447
+ create index _uc_c on iul_cc (a);
448
+ create index cc_named on iul_cc (a);
449
+ -- run
450
+
451
+ -- Unnamed inline unique: auto-name comes from the not-yet-existing column's name.
452
+ alter table iul_cc add column c text unique;
453
+ -- error: would collide with existing index '_uc_c'
454
+
455
+ -- Named inline unique.
456
+ alter table iul_cc add column d text constraint cc_named unique;
457
+ -- error: would collide with existing index 'cc_named'
458
+
459
+ -- The refusals left the table exactly as it was — neither column was added.
460
+ select count(*) as c from table_info('iul_cc') where name in ('c', 'd');
461
+ → [{"c":0}]
462
+
463
+ -- A free name adds the column and its constraint normally.
464
+ alter table iul_cc add column e text unique;
465
+ insert into iul_cc values (1, 'p', 'q');
466
+ -- run
467
+
468
+ insert into iul_cc values (2, 'p2', 'q');
469
+ -- error: unique
470
+
471
+ -- A column declaring several inline `unique` constraints is checked per constraint,
472
+ -- so the colliding one is refused whichever position it holds.
473
+ create table iul_cm (id integer primary key, a text);
474
+ create index _uc_m on iul_cm (a);
475
+ -- run
476
+
477
+ alter table iul_cm add column m text constraint cm_free unique unique;
478
+ -- error: would collide with existing index '_uc_m'
479
+
480
+ select count(*) as c from table_info('iul_cm') where name = 'm';
481
+ → [{"c":0}]
482
+
483
+ -- 9e. `create unique index` still synthesizes its constraint beside its index
484
+ -- (`derivedFromIndex`), which the guard must not mistake for a collision.
485
+ create table iul_ui (id integer primary key, a text);
486
+ create unique index ui_a on iul_ui (a);
487
+ insert into iul_ui values (1, 'k');
488
+ -- run
489
+
490
+ select count(*) as c from schema() where type = 'index' and name = 'ui_a';
491
+ → [{"c":1}]
492
+
493
+ insert into iul_ui values (2, 'k');
494
+ -- error: unique
495
+
496
+ -- 9f. ALTER TABLE RENAME COLUMN — the `_uc_<cols>` name MOVES with the column
497
+ -- The auto-name is derived from the covered columns' CURRENT names and recorded
498
+ -- nowhere, so renaming a covered column renames the backing structure. Two halves:
499
+ -- the structure must follow (or it is orphaned under the old name — surfacing as a
500
+ -- user index, droppable, and no longer recognized as the constraint's), and the
501
+ -- statement must be refused when the post-rename name is already an index here.
502
+ -- Column names are file-unique so a `drop index` below cannot resolve to some other
503
+ -- table's index of the same name (see section 6).
504
+ create table iul_rk (id integer primary key, rka text, rkb text, unique (rka));
505
+ insert into iul_rk values (1, 'x', 'p'), (2, 'y', 'q');
506
+ -- run
507
+
508
+ alter table iul_rk rename column rka to rkz;
509
+ -- run
510
+
511
+ -- The structure followed the column: nothing surfaces under either name.
512
+ select count(*) as c from schema() where type = 'index' and tbl_name = 'iul_rk';
513
+ → [{"c":0}]
514
+
515
+ select count(*) as c from index_info('iul_rk');
516
+ → [{"c":0}]
517
+
518
+ drop index _uc_rka;
519
+ -- error: no such index
520
+
521
+ drop index _uc_rkz;
522
+ -- error: no such index
523
+
524
+ -- ...and the constraint still enforces, now over the new column name.
525
+ insert into iul_rk values (3, 'x', 'r');
526
+ -- error: unique
527
+
528
+ -- Multi-column: the auto-name joins the NEW column names.
529
+ create table iul_rm (id integer primary key, rma text, rmb text, unique (rma, rmb));
530
+ insert into iul_rm values (1, 'x', 'p');
531
+ -- run
532
+
533
+ alter table iul_rm rename column rma to rmz;
534
+ -- run
535
+
536
+ select count(*) as c from index_info('iul_rm');
537
+ → [{"c":0}]
538
+
539
+ drop index _uc_rmz_rmb;
540
+ -- error: no such index
541
+
542
+ insert into iul_rm values (2, 'x', 'p');
543
+ -- error: unique
544
+
545
+ -- Collision: the post-rename name is already an index on this table.
546
+ create table iul_rx (id integer primary key, rxa text, rxb text, unique (rxa));
547
+ create index _uc_rxz on iul_rx (rxb);
548
+ insert into iul_rx values (1, 'x', 'p'), (2, 'y', 'q');
549
+ -- run
550
+
551
+ alter table iul_rx rename column rxa to rxz;
552
+ -- error: would collide with existing index '_uc_rxz'
553
+
554
+ -- Nothing moved: the column keeps its old name...
555
+ select count(*) as c from table_info('iul_rx') where name = 'rxa';
556
+ → [{"c":1}]
557
+
558
+ select count(*) as c from table_info('iul_rx') where name = 'rxz';
559
+ → [{"c":0}]
560
+
561
+ -- ...the user's index is still listed and still resolves rows...
562
+ select count(*) as c from schema() where type = 'index' and name = '_uc_rxz';
563
+ → [{"c":1}]
564
+
565
+ select id from iul_rx where rxb = 'q';
566
+ → [{"id":2}]
567
+
568
+ -- ...the constraint still enforces over the old column name...
569
+ insert into iul_rx values (3, 'x', 'r');
570
+ -- error: unique
571
+
572
+ -- ...and the index is still the user's to drop.
573
+ drop index _uc_rxz;
574
+ -- run
575
+
576
+ -- A case-only rename derives a case-equal name, which must not be read as a
577
+ -- collision with the constraint's OWN structure.
578
+ create table iul_rf (id integer primary key, rfa text, unique (rfa));
579
+ insert into iul_rf values (1, 'x');
580
+ -- run
581
+
582
+ alter table iul_rf rename column rfa to RFA;
583
+ -- run
584
+
585
+ select count(*) as c from index_info('iul_rf');
586
+ → [{"c":0}]
587
+
588
+ insert into iul_rf values (2, 'x');
589
+ -- error: unique
590
+
591
+ -- An EXPOSED implicit structure follows the rename too — it stays visible, under the
592
+ -- NEW derived name (exposure changes visibility, not who owns the name).
593
+ create table iul_rp (id integer primary key, rpa text,
594
+ unique (rpa) with tags ("quereus.expose_implicit_index" = true));
595
+ insert into iul_rp values (1, 'x');
596
+ -- run
597
+
598
+ alter table iul_rp rename column rpa to rpz;
599
+ -- run
600
+
601
+ select index_name from index_info('iul_rp');
602
+ → [{"index_name":"_uc_rpz"}]
603
+
604
+ insert into iul_rp values (2, 'x');
605
+ -- error: unique
606
+
607
+ -- A NAMED UNIQUE's structure takes the CONSTRAINT's name, which no column rename
608
+ -- moves — so neither arm applies to it.
609
+ create table iul_rn (id integer primary key, rna text, rnb text, constraint rn_uq unique (rna));
610
+ create index _uc_rnz on iul_rn (rnb);
611
+ insert into iul_rn values (1, 'x', 'p');
612
+ -- run
613
+
614
+ alter table iul_rn rename column rna to rnz;
615
+ -- run
616
+
617
+ -- The index the unnamed case would have collided with is untouched...
618
+ select count(*) as c from schema() where type = 'index' and name = '_uc_rnz' and tbl_name = 'iul_rn';
619
+ → [{"c":1}]
620
+
621
+ -- ...and the constraint's own structure is still hidden and still enforcing.
622
+ select count(*) as c from index_info('iul_rn') where index_name = 'rn_uq';
623
+ → [{"c":0}]
624
+
625
+ insert into iul_rn values (2, 'x', 'q');
626
+ -- error: unique
627
+
628
+ -- The re-derived name is also the key DROP COLUMN tears the structure down by, so a
629
+ -- rename followed by dropping a covered column must leave no orphan behind. Multi-column
630
+ -- on purpose: a single-column structure collapses to zero key columns and is dropped by
631
+ -- the shift helper regardless, so only this shape proves the key was re-derived.
632
+ create table iul_rd (id integer primary key, rda text, rdb text, unique (rda, rdb));
633
+ insert into iul_rd values (1, 'x', 'p');
634
+ -- run
635
+
636
+ alter table iul_rd rename column rda to rdz;
637
+ -- run
638
+
639
+ alter table iul_rd drop column rdz;
640
+ -- run
641
+
642
+ select count(*) as c from index_info('iul_rd');
643
+ → [{"c":0}]
644
+
645
+ select count(*) as c from schema() where type = 'index' and tbl_name = 'iul_rd';
646
+ → [{"c":0}]
647
+
648
+ -- The constraint covered the dropped column, so it went with it — the survivor is free.
649
+ insert into iul_rd values (2, 'p');
650
+ -- run
651
+
652
+ select count(*) as c from iul_rd;
653
+ → [{"c":2}]
654
+
655
+ -- Renaming INSIDE an open transaction that has already staged a row: the structure moves
656
+ -- to its new name under the transaction's own layer too, so the staged row stays visible
657
+ -- to it, enforcement never lapses, and the commit does not drop the staged write.
658
+ create table iul_rs (id integer primary key, rsa text, unique (rsa));
659
+ insert into iul_rs values (1, 'x');
660
+ -- run
661
+
662
+ begin;
663
+ -- run
664
+ insert into iul_rs values (2, 'y');
665
+ -- run
666
+ alter table iul_rs rename column rsa to rsz;
667
+ -- run
668
+
669
+ insert into iul_rs values (3, 'y');
670
+ -- error: unique
671
+
672
+ commit;
673
+ -- run
674
+
675
+ select id, rsz from iul_rs order by id;
676
+ → [{"id":1,"rsz":"x"},{"id":2,"rsz":"y"}]
677
+
678
+ select count(*) as c from index_info('iul_rs');
679
+ → [{"c":0}]
680
+
681
+ insert into iul_rs values (4, 'x');
682
+ -- error: unique
683
+
684
+ drop table iul_rs;
685
+ drop table iul_rd;
686
+ drop table iul_rn;
687
+ drop table iul_rp;
688
+ drop table iul_rf;
689
+ drop table iul_rx;
690
+ drop table iul_rm;
691
+ drop table iul_rk;
692
+ drop table iul_ui;
693
+ drop table iul_cm;
694
+ drop table iul_cc;
695
+ drop table iul_rc;
696
+ drop table iul_am;
697
+ drop table iul_au;
698
+ drop table iul_ac;
699
+ -- run
700
+
701
+ -- ============================================================
702
+ -- 10. Duplicate UNIQUE constraints — one rule, every declaration site
703
+ -- ============================================================
704
+ -- An unnamed UNIQUE has no name to compare, so before this guard the only thing that
705
+ -- ever noticed a repeat was the backing-structure name — which the memory backend
706
+ -- materializes and the store backend does not, so one statement got three different
707
+ -- answers. The test is the constraint's COLUMN SET, which both backends carry, and it
708
+ -- runs ahead of the index-name check so the message names the constraint rather than
709
+ -- an index the user never created.
710
+
711
+ -- 10a. ALTER TABLE ADD CONSTRAINT
712
+ create table dup_ac (id integer primary key, c integer, d integer);
713
+ alter table dup_ac add unique (c);
714
+ -- run
715
+
716
+ alter table dup_ac add unique (c);
717
+ -- error: an equivalent UNIQUE constraint on (c) already exists
718
+
719
+ -- Case folds, like every other column-name compare.
720
+ alter table dup_ac add unique (C);
721
+ -- error: an equivalent UNIQUE constraint on (C) already exists
722
+
723
+ -- Exactly one constraint survives the refusals, and it still enforces.
724
+ select count(*) as c from unique_constraint_info('dup_ac');
725
+ → [{"c":1}]
726
+
727
+ insert into dup_ac values (1, 5, 7);
728
+ -- run
729
+
730
+ insert into dup_ac values (2, 5, 8);
731
+ -- error: unique
732
+
733
+ -- Column ORDER is not identity: `unique (c, d)` and `unique (d, c)` are one rule, even
734
+ -- though their derived structure names (`_uc_c_d` / `_uc_d_c`) differ.
735
+ alter table dup_ac add unique (c, d);
736
+ -- run
737
+
738
+ alter table dup_ac add unique (d, c);
739
+ -- error: an equivalent UNIQUE constraint on (d, c) already exists
740
+
741
+ select count(distinct id) as c from unique_constraint_info('dup_ac');
742
+ → [{"c":2}]
743
+
744
+ -- 10b. ALTER TABLE ADD COLUMN … unique, repeated within ONE statement
745
+ create table dup_cc (id integer primary key, a text);
746
+ -- run
747
+
748
+ alter table dup_cc add column m text unique unique;
749
+ -- error: an equivalent UNIQUE constraint on (m) already exists
750
+
751
+ -- The refusal left the table exactly as it was — the column was never added.
752
+ select count(*) as c from table_info('dup_cc') where name = 'm';
753
+ → [{"c":0}]
754
+
755
+ -- A single inline `unique` still adds the column and enforces.
756
+ alter table dup_cc add column n text unique;
757
+ insert into dup_cc values (1, 'p', 'q');
758
+ -- run
759
+
760
+ insert into dup_cc values (2, 'p2', 'q');
761
+ -- error: unique
762
+
763
+ -- 10c. CREATE TABLE — refused before any storage is created
764
+ create table dup_ct (id integer primary key, c integer, unique (c), unique (c));
765
+ -- error: an equivalent UNIQUE constraint on (c) already exists
766
+
767
+ select count(*) as c from schema() where type = 'table' and name = 'dup_ct';
768
+ → [{"c":0}]
769
+
770
+ -- Column-level and table-level spellings of the same rule collide too.
771
+ create table dup_ct (id integer primary key, c integer unique, unique (c));
772
+ -- error: an equivalent UNIQUE constraint on (c) already exists
773
+
774
+ -- ...as do two column-level ones on one column.
775
+ create table dup_ct (id integer primary key, c integer unique unique);
776
+ -- error: an equivalent UNIQUE constraint on (c) already exists
777
+
778
+ -- Multi-column, declared in opposite orders — still one rule.
779
+ create table dup_ct (id integer primary key, c integer, d integer, unique (c, d), unique (d, c));
780
+ -- error: an equivalent UNIQUE constraint on (d, c) already exists
781
+
782
+ -- The name is free the whole time: a single declaration creates normally.
783
+ create table dup_ct (id integer primary key, c integer, d integer, unique (c), unique (d));
784
+ insert into dup_ct values (1, 1, 1);
785
+ -- run
786
+
787
+ insert into dup_ct values (2, 1, 2);
788
+ -- error: unique
789
+
790
+ -- 10d. CARVE-OUT — a named UNIQUE beside an unnamed one over the same columns is legal
791
+ -- Both are addressable and droppable by name, so the state is redundant, not broken.
792
+ create table dup_nm (id integer primary key, c integer, unique (c));
793
+ alter table dup_nm add constraint nm_u1 unique (c);
794
+ -- run
795
+
796
+ select count(distinct id) as c from unique_constraint_info('dup_nm');
797
+ → [{"c":2}]
798
+
799
+ -- A SECOND named one is legal as well — a distinct name is a distinct identity.
800
+ alter table dup_nm add constraint nm_u2 unique (c);
801
+ -- run
802
+
803
+ -- ...but a repeated NAME is still refused, by the name guard rather than this one.
804
+ alter table dup_nm add constraint nm_u2 unique (c);
805
+ -- error: a constraint with that name already exists
806
+
807
+ -- The named ones drop by name, leaving the unnamed one enforcing.
808
+ alter table dup_nm drop constraint nm_u1;
809
+ alter table dup_nm drop constraint nm_u2;
810
+ insert into dup_nm values (1, 3);
811
+ -- run
812
+
813
+ insert into dup_nm values (2, 3);
814
+ -- error: unique
815
+
816
+ select count(distinct id) as c from unique_constraint_info('dup_nm');
817
+ → [{"c":1}]
818
+
819
+ -- 10e. CARVE-OUT — partial UNIQUEs over the same columns are different rules
820
+ -- A partial UNIQUE only governs rows inside its predicate, so two with different
821
+ -- predicates are genuinely different constraints, and neither duplicates a full one.
822
+ create table dup_pt (id integer primary key, c integer, s text);
823
+ create unique index dup_pt_pos on dup_pt (c) where s = 'pos';
824
+ create unique index dup_pt_neg on dup_pt (c) where s = 'neg';
825
+ -- run
826
+
827
+ select count(distinct id) as c from unique_constraint_info('dup_pt') where partial = 1;
828
+ → [{"c":2}]
829
+
830
+ -- A full UNIQUE over the same column is still accepted beside them, and builds its
831
+ -- OWN backing structure rather than adopting either partial index — a partial index
832
+ -- only covers rows inside its predicate, so it cannot back a rule that covers every row.
833
+ alter table dup_pt add unique (c);
834
+ -- run
835
+
836
+ select count(distinct id) as c from unique_constraint_info('dup_pt');
837
+ → [{"c":3}]
838
+
839
+ -- ...and it enforces across the WHOLE table, not just the rows either partial
840
+ -- index's predicate admits.
841
+ insert into dup_pt values (1, 5, 'a');
842
+ -- run
843
+
844
+ insert into dup_pt values (2, 5, 'b');
845
+ -- error: unique
846
+
847
+ -- ...and now it is the full one that refuses the repeat.
848
+ alter table dup_pt add unique (c);
849
+ -- error: an equivalent UNIQUE constraint on (c) already exists
850
+
851
+ -- 10e-bis. The full UNIQUE keeps enforcing after the index it ADOPTED is dropped,
852
+ -- and still refuses to fall back onto a same-column PARTIAL index.
853
+ -- `add unique (c)` reuses `reuse_full` (unique, unfiltered, same collation) instead of
854
+ -- building its own structure. Dropping that index leaves the constraint with no named
855
+ -- structure, so enforcement resolves the covering index by column set — where a partial
856
+ -- index over (c) is the only remaining candidate. It must be refused: with it adopted,
857
+ -- both rows below sit outside `s = 'pos'` and the duplicate `c = 7` goes unnoticed.
858
+ create table reuse_pt (id integer primary key, c integer, s text);
859
+ create unique index reuse_part on reuse_pt (c) where s = 'pos';
860
+ create unique index reuse_full on reuse_pt (c);
861
+ alter table reuse_pt add unique (c);
862
+ drop index reuse_full;
863
+ -- run
864
+
865
+ insert into reuse_pt values (1, 7, 'a');
866
+ -- run
867
+
868
+ insert into reuse_pt values (2, 7, 'b');
869
+ -- error: unique
870
+
871
+ select count(*) as c from reuse_pt;
872
+ → [{"c":1}]
873
+
874
+ -- The partial index still enforces its own narrower rule alongside.
875
+ insert into reuse_pt values (3, 8, 'pos');
876
+ -- run
877
+
878
+ insert into reuse_pt values (4, 8, 'pos');
879
+ -- error: unique
880
+
881
+ -- 10f. A column name is compared as a whole name, never as text spliced together
882
+ -- A quoted identifier may contain anything, including whatever character the guard
883
+ -- uses to concatenate a column set. `unique ("a,b")` covers ONE column and must not
884
+ -- read as the pair `(a, b)`; refusing it would turn away a legal declaration.
885
+ create table dup_sep (id integer primary key, "a,b" integer, a integer, b integer, unique (a, b));
886
+ alter table dup_sep add unique ("a,b");
887
+ -- run
888
+
889
+ select count(distinct id) as c from unique_constraint_info('dup_sep');
890
+ → [{"c":2}]
891
+
892
+ -- Both enforce, independently.
893
+ insert into dup_sep values (1, 1, 1, 1);
894
+ -- run
895
+
896
+ insert into dup_sep values (2, 2, 1, 1);
897
+ -- error: unique
898
+
899
+ insert into dup_sep values (3, 1, 3, 3);
900
+ -- error: unique
901
+
902
+ -- ...and the repeat of the single-column one is still refused.
903
+ alter table dup_sep add unique ("a,b");
904
+ -- error: an equivalent UNIQUE constraint on (a,b) already exists
905
+
906
+ drop table dup_sep;
907
+ drop table dup_pt;
908
+ drop table dup_nm;
909
+ drop table dup_ct;
910
+ drop table dup_cc;
911
+ drop table dup_ac;
912
+ -- run
913
+
914
+ -- ============================================================
915
+ -- 11. Two UNIQUE constraints may not derive ONE backing structure name
916
+ -- ============================================================
917
+ -- A UNIQUE's backing structure is named after the constraint, or `_uc_<cols>` when
918
+ -- unnamed. Nothing in that rule stops two constraints on one table deriving the SAME
919
+ -- name. Two spellings reach it: a constraint name written with the engine's reserved
920
+ -- `_uc_` prefix, and the `_`-joined auto-name colliding on ordinary column names
921
+ -- (`unique (a_b)` beside `unique (a, b)` — both derive `_uc_a_b`). Left unguarded the
922
+ -- second constraint shares the first's structure and is then enforced by one keyed on
923
+ -- the OTHER constraint's columns, so it silently stops rejecting duplicates.
924
+ --
925
+ -- Refused on every authoring path. The comparison reads `uniqueConstraints`, which
926
+ -- BOTH backends carry, rather than a materialized index (memory has one, store does
927
+ -- not) — so the message below is identical on both, which is the point of this section.
928
+
929
+ -- 11a. CREATE TABLE — a constraint name carrying the reserved `_uc_` prefix
930
+ create table cn_ct (id integer primary key, cn_c integer, cn_b integer,
931
+ constraint _uc_cn_c unique (cn_b), unique (cn_c));
932
+ -- error: both derive backing structure name '_uc_cn_c'
933
+
934
+ -- Refused before any storage is created.
935
+ select count(*) as c from schema() where type = 'table' and name = 'cn_ct';
936
+ → [{"c":0}]
937
+
938
+ -- Case folds, like every other index-name compare.
939
+ create table cn_ct (id integer primary key, cn_c integer, cn_b integer,
940
+ constraint _UC_CN_C unique (cn_b), unique (cn_c));
941
+ -- error: both derive backing structure name
942
+
943
+ -- Two inline `unique` constraints on ONE column, the written name coinciding with the
944
+ -- auto-name the other derives. The CREATE TABLE twin of the ADD COLUMN case in 11d —
945
+ -- neither constraint can be seen on a table, so only comparing the declaration against
946
+ -- itself catches it.
947
+ create table cn_ct (id integer primary key, cn_c integer constraint _uc_cn_c unique unique);
948
+ -- error: both derive backing structure name '_uc_cn_c'
949
+
950
+ select count(*) as c from schema() where type = 'table' and name = 'cn_ct';
951
+ → [{"c":0}]
952
+
953
+ -- 11b. CREATE TABLE — ordinary column names, no reserved prefix anywhere
954
+ -- The auto-name joins the covered column names with `_`, so the single column `cnq_cnr`
955
+ -- derives exactly what the pair `(cnq, cnr)` derives.
956
+ create table cn_ab (id integer primary key, cnq_cnr integer, cnq integer, cnr integer,
957
+ unique (cnq_cnr), unique (cnq, cnr));
958
+ -- error: both derive backing structure name '_uc_cnq_cnr'
959
+
960
+ select count(*) as c from schema() where type = 'table' and name = 'cn_ab';
961
+ → [{"c":0}]
962
+
963
+ -- Column-level and table-level spellings meet in the same comparison.
964
+ create table cn_ab (id integer primary key, cnq_cnr integer unique, cnq integer, cnr integer,
965
+ unique (cnq, cnr));
966
+ -- error: both derive backing structure name '_uc_cnq_cnr'
967
+
968
+ -- Neither declaration alone is a problem: free names still create and enforce.
969
+ create table cn_ab (id integer primary key, cnq_cnr integer, cnq integer, cnr integer,
970
+ unique (cnq_cnr), unique (cnq));
971
+ insert into cn_ab values (1, 1, 1, 1);
972
+ -- run
973
+
974
+ insert into cn_ab values (2, 1, 2, 2);
975
+ -- error: unique
976
+
977
+ insert into cn_ab values (3, 3, 1, 3);
978
+ -- error: unique
979
+
980
+ -- 11c. ALTER TABLE ADD CONSTRAINT — colliding with another CONSTRAINT's structure
981
+ -- (not an index), so the refusal must come from the constraint comparison on BOTH
982
+ -- backends rather than from memory's materialized index entry.
983
+ create table cn_ac (id integer primary key, cn_c integer, cn_b integer, unique (cn_c));
984
+ -- run
985
+
986
+ alter table cn_ac add constraint _uc_cn_c unique (cn_b);
987
+ -- error: would collide with the backing structure of the UNIQUE constraint on (cn_c)
988
+
989
+ -- Nothing was installed by the refusal.
990
+ select count(distinct id) as c from unique_constraint_info('cn_ac');
991
+ → [{"c":1}]
992
+
993
+ -- The mirror spelling: the reserved-prefix NAME is on the table and the auto-name is
994
+ -- the one being declared. The message names the other constraint the way it was written.
995
+ create table cn_am (id integer primary key, cn_m integer, cn_n integer,
996
+ constraint _uc_cn_m unique (cn_n));
997
+ -- run
998
+
999
+ alter table cn_am add unique (cn_m);
1000
+ -- error: would collide with the backing structure of the UNIQUE constraint '_uc_cn_m'
1001
+
1002
+ select count(distinct id) as c from unique_constraint_info('cn_am');
1003
+ → [{"c":1}]
1004
+
1005
+ -- 11d. ALTER TABLE ADD COLUMN … unique
1006
+ create table cn_cc (id integer primary key, cn_q integer, constraint _uc_c unique (cn_q));
1007
+ -- run
1008
+
1009
+ alter table cn_cc add column c text unique;
1010
+ -- error: would collide with the backing structure of the UNIQUE constraint '_uc_c'
1011
+
1012
+ -- The refusal left the table exactly as it was — the column was never added.
1013
+ select count(*) as c from table_info('cn_cc') where name = 'c';
1014
+ → [{"c":0}]
1015
+
1016
+ -- Two inline `unique` constraints on ONE new column can collide with EACH OTHER, and
1017
+ -- neither is on the table yet — so only comparing them against one another catches it.
1018
+ create table cn_ci (id integer primary key, cn_r integer);
1019
+ -- run
1020
+
1021
+ alter table cn_ci add column c text constraint _uc_c unique unique;
1022
+ -- error: both derive backing structure name '_uc_c'
1023
+
1024
+ select count(*) as c from table_info('cn_ci') where name = 'c';
1025
+ → [{"c":0}]
1026
+
1027
+ -- 11e. ALTER TABLE RENAME COLUMN — the rename is what makes the two names meet
1028
+ -- `_uc_<cols>` is derived from the covered columns' CURRENT names, so renaming a
1029
+ -- covered column moves the structure onto a name another constraint already derives.
1030
+ create table cn_rk (id integer primary key, cnk_cnm integer, cnk integer, cnx integer,
1031
+ unique (cnk_cnm), unique (cnk, cnx));
1032
+ insert into cn_rk values (1, 1, 1, 1);
1033
+ -- run
1034
+
1035
+ alter table cn_rk rename column cnx to cnm;
1036
+ -- error: would collide with the backing structure of the UNIQUE constraint on (cnk_cnm)
1037
+
1038
+ -- Nothing moved: the column keeps its old name...
1039
+ select count(*) as c from table_info('cn_rk') where name = 'cnx';
1040
+ → [{"c":1}]
1041
+
1042
+ select count(*) as c from table_info('cn_rk') where name = 'cnm';
1043
+ → [{"c":0}]
1044
+
1045
+ -- ...and both constraints still enforce, each under its own structure.
1046
+ insert into cn_rk values (2, 1, 2, 2);
1047
+ -- error: unique
1048
+
1049
+ insert into cn_rk values (3, 3, 1, 1);
1050
+ -- error: unique
1051
+
1052
+ -- 11f. ALTER TABLE RENAME CONSTRAINT onto another constraint's derived name
1053
+ create table cn_rn (id integer primary key, cn_m integer, cn_n integer,
1054
+ unique (cn_m), constraint cn_u unique (cn_n));
1055
+ -- run
1056
+
1057
+ alter table cn_rn rename constraint cn_u to _uc_cn_m;
1058
+ -- error: would collide with the backing structure of the UNIQUE constraint on (cn_m)
1059
+
1060
+ -- Neither constraint moved.
1061
+ select count(*) as c from unique_constraint_info('cn_rn') where name = 'cn_u';
1062
+ → [{"c":1}]
1063
+
1064
+ -- A free name still renames.
1065
+ alter table cn_rn rename constraint cn_u to cn_u_moved;
1066
+ -- run
1067
+
1068
+ select count(*) as c from unique_constraint_info('cn_rn') where name = 'cn_u_moved';
1069
+ → [{"c":1}]
1070
+
1071
+ -- 11g. NEGATIVE CONTROLS — legal shapes that must keep working
1072
+ -- Two DIFFERENTLY-named UNIQUEs over the same columns: distinct names, distinct
1073
+ -- structures. Redundant, not broken.
1074
+ create table cn_ok1 (id integer primary key, cn_c1 integer,
1075
+ constraint cn_u1 unique (cn_c1), constraint cn_u2 unique (cn_c1));
1076
+ insert into cn_ok1 values (1, 5);
1077
+ -- run
1078
+
1079
+ select count(distinct id) as c from unique_constraint_info('cn_ok1');
1080
+ → [{"c":2}]
1081
+
1082
+ insert into cn_ok1 values (2, 5);
1083
+ -- error: unique
1084
+
1085
+ -- A named UNIQUE beside an unnamed one over the same columns — `_uc_<cols>` and the
1086
+ -- name are different names, so nothing collides.
1087
+ create table cn_ok2 (id integer primary key, cn_c2 integer,
1088
+ unique (cn_c2), constraint cn_u3 unique (cn_c2));
1089
+ insert into cn_ok2 values (1, 5);
1090
+ -- run
1091
+
1092
+ select count(distinct id) as c from unique_constraint_info('cn_ok2');
1093
+ → [{"c":2}]
1094
+
1095
+ insert into cn_ok2 values (2, 5);
1096
+ -- error: unique
1097
+
1098
+ -- A UNIQUE whose columns are already covered by a user index of a DIFFERENT name:
1099
+ -- structure reuse matches on COLUMNS, never on names, so the declaration stands.
1100
+ create table cn_ok3 (id integer primary key, cn_c3 integer);
1101
+ create index cn_ok3_i on cn_ok3 (cn_c3);
1102
+ alter table cn_ok3 add unique (cn_c3);
1103
+ insert into cn_ok3 values (1, 5);
1104
+ -- run
1105
+
1106
+ select count(*) as c from schema() where type = 'index' and name = 'cn_ok3_i';
1107
+ → [{"c":1}]
1108
+
1109
+ insert into cn_ok3 values (2, 5);
1110
+ -- error: unique
1111
+
1112
+ -- A constraint synthesized by `create unique index` is SKIPPED by the constraint
1113
+ -- comparison: its backing structure IS that index, already compared by the index arm,
1114
+ -- so matching it here would report one collision twice under two messages. The proof is
1115
+ -- the message — a declaration onto that name is refused as an INDEX collision.
1116
+ create table cn_ok4 (id integer primary key, cn_c4 integer, cn_d4 integer);
1117
+ create unique index _uc_cn_d4 on cn_ok4 (cn_c4);
1118
+ insert into cn_ok4 values (1, 5, 6);
1119
+ -- run
1120
+
1121
+ alter table cn_ok4 add unique (cn_d4);
1122
+ -- error: would collide with existing index '_uc_cn_d4'
1123
+
1124
+ insert into cn_ok4 values (2, 5, 7);
1125
+ -- error: unique
1126
+
1127
+ -- Same for a PARTIAL one. Only `create unique index … where` produces a partial UNIQUE
1128
+ -- constraint today, so a partial's structure is always its own index and the index arm
1129
+ -- owns the refusal — but the name may still not be shared.
1130
+ create table cn_ok5 (id integer primary key, cn_c5 integer, cn_s5 text, cn_d5 integer);
1131
+ create unique index _uc_cn_d5 on cn_ok5 (cn_c5) where cn_s5 = 'pos';
1132
+ -- run
1133
+
1134
+ alter table cn_ok5 add unique (cn_d5);
1135
+ -- error: would collide with existing index '_uc_cn_d5'
1136
+
1137
+ -- The partial index still enforces its own narrower rule.
1138
+ insert into cn_ok5 values (1, 5, 'pos', 1);
1139
+ -- run
1140
+
1141
+ insert into cn_ok5 values (2, 5, 'pos', 2);
1142
+ -- error: unique
1143
+
1144
+ insert into cn_ok5 values (3, 5, 'neg', 3);
1145
+ -- run
1146
+
1147
+ drop table cn_ok5;
1148
+ drop table cn_ok4;
1149
+ drop table cn_ok3;
1150
+ drop table cn_ok2;
1151
+ drop table cn_ok1;
1152
+ drop table cn_rn;
1153
+ drop table cn_rk;
1154
+ drop table cn_ci;
1155
+ drop table cn_cc;
1156
+ drop table cn_am;
1157
+ drop table cn_ac;
1158
+ drop table cn_ab;
1159
+ -- run
1160
+
1161
+ -- ============================================================
1162
+ -- 12. `apply schema` reaches the same guard
1163
+ -- ============================================================
1164
+ -- The differ emits `ALTER TABLE … ADD <constraint>` through the normal statement
1165
+ -- pipeline, so it inherits the ADD CONSTRAINT arm above rather than needing its
1166
+ -- own check. Runs last, against an otherwise-empty main schema, so the
1167
+ -- declaration below is the whole of main and the diff generates no drops.
1168
+
1169
+ create table as_t (id integer primary key, a text, b text);
1170
+ create index as_foo on as_t (b);
1171
+ insert into as_t values (1, 'x', 'p'), (2, 'y', 'q');
1172
+ -- run
1173
+
1174
+ declare schema main {
1175
+ table as_t (id integer primary key, a text, b text, constraint as_foo unique (a))
1176
+ index as_foo on as_t (b)
1177
+ }
1178
+ -- run
1179
+
1180
+ apply schema main;
1181
+ -- error: would collide with existing index 'as_foo'
1182
+
1183
+ -- The migration stopped at the offending statement; the index is intact.
1184
+ select count(*) as c from schema() where type = 'index' and name = 'as_foo';
1185
+ → [{"c":1}]
1186
+
1187
+ select id from as_t where b = 'q';
1188
+ → [{"id":2}]
1189
+
1190
+ drop table as_t;
1191
+ -- run
1192
+
1193
+ -- The CREATE TABLE arm reaches its guard the same way: a declared table carrying the
1194
+ -- same plain UNIQUE twice is emitted as an ordinary CREATE TABLE and refused there.
1195
+ declare schema main {
1196
+ table as_dup (id integer primary key, c integer, unique (c), unique (c))
1197
+ }
1198
+ -- run
1199
+
1200
+ apply schema main;
1201
+ -- error: an equivalent UNIQUE constraint on (c) already exists
1202
+
1203
+ select count(*) as c from schema() where type = 'table' and name = 'as_dup';
1204
+ → [{"c":0}]
1205
+
1206
+ -- ...and so does a declared table whose two UNIQUEs derive one backing structure name.
1207
+ declare schema main {
1208
+ table as_bn (id integer primary key, as_c integer, as_b integer, constraint _uc_as_c unique (as_b), unique (as_c))
1209
+ }
1210
+ -- run
1211
+
1212
+ apply schema main;
1213
+ -- error: both derive backing structure name '_uc_as_c'
1214
+
1215
+ select count(*) as c from schema() where type = 'table' and name = 'as_bn';
1216
+ → [{"c":0}]