@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,960 @@
1
+ -- 15.1-semantic-ordering.sqllogic - Semantic ordering for TIMESPAN and JSON
2
+ --
3
+ -- The rule (docs/types.md "Semantic ordering"): wherever a value of a declared
4
+ -- semantic-ordering logical type is ordered or compared — ORDER BY, comparison
5
+ -- operators, BETWEEN, PK order and range scans, DISTINCT/GROUP BY/set-operation
6
+ -- identity, window ordering/partitioning, join keys — the type's compare is the
7
+ -- order. TIMESPAN orders by elapsed time; JSON by structural deep-compare.
8
+
9
+ -- === TIMESPAN primary key: order, elision, range scan, point seek ===
10
+
11
+ create table m (d timespan primary key);
12
+ insert into m values ('PT2H'), ('PT90M');
13
+
14
+ -- Elapsed-time order: 90 minutes < 2 hours, though the text 'PT2H' < 'PT90M'.
15
+ -- (This ORDER BY may be satisfied by the PK order — elided sort — or by a real
16
+ -- sort; both must produce elapsed-time order.)
17
+ select d from m order by d;
18
+ → [{"d":"PT90M"},{"d":"PT2H"}]
19
+
20
+ select d from m order by d desc;
21
+ → [{"d":"PT2H"},{"d":"PT90M"}]
22
+
23
+ -- Range scan through the PK BTree: the bound filter must agree with the typed
24
+ -- tree order (previously returned no rows — text-order filter over a
25
+ -- elapsed-time-ordered tree produced an empty window).
26
+ select d from m where d > 'PT90M';
27
+ → [{"d":"PT2H"}]
28
+
29
+ select d from m where d < 'PT2H';
30
+ → [{"d":"PT90M"}]
31
+
32
+ select d from m where d >= 'PT90M' order by d;
33
+ → [{"d":"PT90M"},{"d":"PT2H"}]
34
+
35
+ -- Point seek with an equal-elapsed-time spelling: PT120M ≡ PT2H.
36
+ select d from m where d = 'PT120M';
37
+ → [{"d":"PT2H"}]
38
+
39
+ -- BETWEEN agrees with the desugared comparisons: [30m, 100m] contains 90m only.
40
+ -- (Text order would exclude PT90M: 'PT90M' > 'PT100M' lexicographically.)
41
+ select d from m where d between 'PT30M' and 'PT100M';
42
+ → [{"d":"PT90M"}]
43
+
44
+ drop table m;
45
+
46
+ -- === TIMESPAN non-key column: real Sort node, comparisons ===
47
+
48
+ create table s (id integer primary key, d timespan);
49
+ insert into s values (1, 'PT2H'), (2, 'PT90M');
50
+
51
+ -- Real sort (no index on d) must match the elided PK-order result above.
52
+ select d from s order by d;
53
+ → [{"d":"PT90M"},{"d":"PT2H"}]
54
+
55
+ select d from s where d > 'PT90M';
56
+ → [{"d":"PT2H"}]
57
+
58
+ select d from s where d between 'PT30M' and 'PT100M';
59
+ → [{"d":"PT90M"}]
60
+
61
+ -- Window ordering ranks by elapsed time.
62
+ select d, row_number() over (order by d) as rn from s order by rn;
63
+ → [{"d":"PT90M","rn":1},{"d":"PT2H","rn":2}]
64
+
65
+ drop table s;
66
+
67
+ -- NULL placement is ORDER BY's rule, never the type's: NULLs first by default in
68
+ -- BOTH directions, and explicit NULLS FIRST/LAST is absolute.
69
+ create table sn (id integer primary key, d timespan null);
70
+ insert into sn values (1, 'PT2H'), (2, 'PT90M'), (3, null);
71
+
72
+ select id from sn order by d;
73
+ → [{"id":3},{"id":2},{"id":1}]
74
+
75
+ select id from sn order by d desc;
76
+ → [{"id":3},{"id":1},{"id":2}]
77
+
78
+ select id from sn order by d nulls last;
79
+ → [{"id":2},{"id":1},{"id":3}]
80
+
81
+ drop table sn;
82
+
83
+ -- === Composite-key access paths over a TIMESPAN column ===
84
+ -- (the secondary-index access path lives in 15.1.2-semantic-ordering-index —
85
+ -- it needs standalone CREATE INDEX)
86
+
87
+ -- Composite PK with a TIMESPAN trailing member: the equality prefix and the
88
+ -- trailing range bound must both compare under the tree's order.
89
+ create table cp (g integer, d timespan, primary key (g, d));
90
+ insert into cp values (1, 'PT2H'), (1, 'PT90M'), (2, 'PT30M');
91
+
92
+ select d from cp where g = 1 order by d;
93
+ → [{"d":"PT90M"},{"d":"PT2H"}]
94
+
95
+ select d from cp where g = 1 and d > 'PT90M';
96
+ → [{"d":"PT2H"}]
97
+
98
+ select d from cp where g = 1 and d = 'PT120M';
99
+ → [{"d":"PT2H"}]
100
+
101
+ drop table cp;
102
+
103
+ -- DESC primary key: the descending physical walk still yields elapsed-time order.
104
+ create table dsc (d timespan, primary key (d desc));
105
+ insert into dsc values ('PT2H'), ('PT90M');
106
+
107
+ select d from dsc order by d;
108
+ → [{"d":"PT90M"},{"d":"PT2H"}]
109
+
110
+ select d from dsc where d > 'PT90M';
111
+ → [{"d":"PT2H"}]
112
+
113
+ drop table dsc;
114
+
115
+ -- IN-list multi-seek: each probe seeks by elapsed time, not by text.
116
+ create table il (d timespan primary key);
117
+ insert into il values ('PT2H'), ('PT90M');
118
+
119
+ select d from il where d in ('PT120M', 'PT30M');
120
+ → [{"d":"PT2H"}]
121
+
122
+ select d from il where d not between 'PT30M' and 'PT100M';
123
+ → [{"d":"PT2H"}]
124
+
125
+ drop table il;
126
+
127
+ -- === TIMESPAN identity: 'PT1H' ≡ 'PT60M' (same elapsed time) ===
128
+
129
+ create table dt (id integer primary key, d timespan);
130
+ insert into dt values (1, 'PT1H'), (2, 'PT60M');
131
+
132
+ -- Equality operator already says they are equal.
133
+ select (timespan('PT1H') = timespan('PT60M')) as r;
134
+ → [{"r":true}]
135
+
136
+ -- DISTINCT through a typed column collapses (representative unspecified — assert
137
+ -- the count, not the surviving text).
138
+ select count(*) as c from (select distinct d from dt) as q;
139
+ → [{"c":1}]
140
+
141
+ -- GROUP BY groups them together (project the key through total-seconds so the
142
+ -- expectation does not depend on which textual representative survives).
143
+ select timespan_total_seconds(d) as s, count(*) as c from dt group by d;
144
+ → [{"s":3600,"c":2}]
145
+
146
+ -- Set-operation identity (UNION dedup) collapses them.
147
+ select count(*) as c from (
148
+ select d from dt where id = 1
149
+ union
150
+ select d from dt where id = 2
151
+ ) as q;
152
+ → [{"c":1}]
153
+
154
+ -- EXCEPT and INTERSECT run over the same row-identity comparator as UNION, so a
155
+ -- differing spelling of the same elapsed time subtracts / intersects as one value.
156
+ select count(*) as c from (
157
+ select d from dt where id = 1
158
+ except
159
+ select d from dt where id = 2
160
+ ) as q;
161
+ → [{"c":0}]
162
+
163
+ select count(*) as c from (
164
+ select d from dt where id = 1
165
+ intersect
166
+ select d from dt where id = 2
167
+ ) as q;
168
+ → [{"c":1}]
169
+
170
+ -- Window PARTITION BY places both rows in one partition.
171
+ select count(*) over (partition by d) as c from dt;
172
+ → [{"c":2},{"c":2}]
173
+
174
+ -- IN membership must agree with the `=` it desugars to, on every emitIn path:
175
+ -- a constant list, a non-constant list element, and a subquery source. `d` is not
176
+ -- the primary key here, so the residual `In` node survives and the comparison is
177
+ -- exercised directly rather than being turned into an index multi-seek.
178
+ select id from dt where d in ('PT60M') order by id;
179
+ → [{"id":1},{"id":2}]
180
+
181
+ select id from dt where d in ((select d from dt where id = 2)) order by id;
182
+ → [{"id":1},{"id":2}]
183
+
184
+ select id from dt where d in (select d from dt where id = 2) order by id;
185
+ → [{"id":1},{"id":2}]
186
+
187
+ -- Non-members still miss, and an unparseable list entry compares as text (no
188
+ -- false match) instead of derailing the rest of the list.
189
+ select id from dt where d in ('PT59M', 'not-a-duration') order by id;
190
+ → []
191
+
192
+ -- A simple CASE compares its base against each WHEN through the same routing, so
193
+ -- `case d when 'PT60M'` must agree with `d = 'PT60M'` and `d in ('PT60M')` above.
194
+ -- Regression for `simple-case-comparison-collation-and-type` (previously the
195
+ -- CASE match compared raw text and reported a miss).
196
+ select id, case d when 'PT60M' then 'hit' else 'miss' end as r from dt order by id;
197
+ → [{"id":1,"r":"hit"},{"id":2,"r":"hit"}]
198
+
199
+ -- Per-clause: a non-member spelling still misses, and a later matching clause wins.
200
+ select id, case d when 'PT59M' then 'hit1' when 'PT3600S' then 'hit2' else 'miss' end as r
201
+ from dt order by id;
202
+ → [{"id":1,"r":"hit2"},{"id":2,"r":"hit2"}]
203
+
204
+ -- Equi-join matches across spellings (any join algorithm must agree with `=`).
205
+ create table jt2 (id integer primary key, d timespan);
206
+ insert into jt2 values (10, 'PT60M');
207
+ select dt.id as l, jt2.id as r from dt join jt2 on dt.d = jt2.d order by l;
208
+ → [{"l":1,"r":10},{"l":2,"r":10}]
209
+
210
+ drop table jt2;
211
+ drop table dt;
212
+
213
+ -- === Mixed-type equi-join keys agree with `=` ===
214
+ --
215
+ -- A join key pairing a semantic-ordering column with a plain one is not a valid
216
+ -- PHYSICAL key: hash/bloom serialize the raw text and merge co-walks two
217
+ -- incompatible orders (elapsed time on one side, text on the other), so both drop
218
+ -- matches that `=` makes. The equi-pair extractor declines such a pair, demoting it
219
+ -- to the residual — or, for USING, sinking the whole extraction — so the generic
220
+ -- join evaluates it with the `=` operator's own semantics. Every spelling below
221
+ -- must therefore agree with the `where` form.
222
+ -- Regression for `mixed-type-equi-join-key-drops-semantic-matches` (all the `on` /
223
+ -- `using` / `exists` forms previously returned no rows).
224
+
225
+ create table mxa (id integer primary key, d timespan);
226
+ create table mxb (id integer primary key, s text);
227
+ insert into mxa values (1, 'PT1H'), (2, 'PT30M');
228
+ insert into mxb values (10, 'PT60M'), (20, 'PT45M');
229
+
230
+ -- Baseline: the comparison in a `where` clause has always matched.
231
+ select mxa.id as l, mxb.id as r from mxa cross join mxb where mxa.d = mxb.s;
232
+ → [{"l":1,"r":10}]
233
+
234
+ -- The same comparison spelled as an ON condition must agree.
235
+ select mxa.id as l, mxb.id as r from mxa join mxb on mxa.d = mxb.s;
236
+ → [{"l":1,"r":10}]
237
+
238
+ -- Flipped operand order resolves to the same pair.
239
+ select mxa.id as l, mxb.id as r from mxa join mxb on mxb.s = mxa.d;
240
+ → [{"l":1,"r":10}]
241
+
242
+ -- Correlated EXISTS (planned as a semi-join over the same extraction).
243
+ select id from mxa where exists (select 1 from mxb where mxb.s = mxa.d);
244
+ → [{"id":1}]
245
+
246
+ -- LEFT JOIN + null test: the unmatched left row must be the one whose elapsed time
247
+ -- has no counterpart, not both of them.
248
+ select mxa.id as l from mxa left join mxb on mxa.d = mxb.s where mxb.id is not null;
249
+ → [{"l":1}]
250
+
251
+ select mxa.id as l from mxa left join mxb on mxa.d = mxb.s where mxb.id is null;
252
+ → [{"l":2}]
253
+
254
+ -- A further filter pinning one side to a constant must not leak into the other
255
+ -- side via the join equality: the logical equi-pair extractor previously minted
256
+ -- a value-level pairing fact even when the two sides disagreed on semantic
257
+ -- ordering, and predicate-inference substituted the pin's constant across the
258
+ -- pair, comparing it as text against the untyped side and dropping the match.
259
+ -- Regression for `equality-fact-extraction-ignores-semantic-ordering`. Each
260
+ -- `on … where` form below must agree with the `cross join … where` oracle.
261
+
262
+ select mxa.id as l, mxb.id as r from mxa cross join mxb where mxa.d = mxb.s and mxa.d = 'PT1H';
263
+ → [{"l":1,"r":10}]
264
+
265
+ select mxa.id as l, mxb.id as r from mxa join mxb on mxa.d = mxb.s where mxa.d = 'PT1H';
266
+ → [{"l":1,"r":10}]
267
+
268
+ select mxa.id as l, mxb.id as r from mxa join mxb on mxb.s = mxa.d where mxa.d = 'PT1H';
269
+ → [{"l":1,"r":10}]
270
+
271
+ select mxa.id as l, mxb.id as r from mxa cross join mxb where mxa.d = mxb.s and mxb.s = 'PT60M';
272
+ → [{"l":1,"r":10}]
273
+
274
+ select mxa.id as l, mxb.id as r from mxa join mxb on mxa.d = mxb.s where mxb.s = 'PT60M';
275
+ → [{"l":1,"r":10}]
276
+
277
+ select mxa.id as l, mxb.id as r from mxa join mxb on mxb.s = mxa.d where mxb.s = 'PT60M';
278
+ → [{"l":1,"r":10}]
279
+
280
+ -- Pinning the TEXT side to a spelling that only matches under semantic-ordering
281
+ -- comparison, not as plain text, correctly finds nothing — this asymmetry is
282
+ -- intended, not a regression.
283
+ select mxa.id as l, mxb.id as r from mxa cross join mxb where mxa.d = mxb.s and mxb.s = 'PT1H';
284
+ → []
285
+
286
+ select mxa.id as l, mxb.id as r from mxa join mxb on mxa.d = mxb.s where mxb.s = 'PT1H';
287
+ → []
288
+
289
+ drop table mxb;
290
+ drop table mxa;
291
+
292
+ -- === Filter-level cross-column equality facts ===
293
+ --
294
+ -- The join extractors above are gated on semantic ordering; `extractEqualityFds`
295
+ -- (the FILTER-side extractor) was not, so `where d = s` over a single table minted
296
+ -- mirror FDs and an equivalence class claiming the timespan column and the text
297
+ -- column always hold the same value. They do not: 'PT1H' and 'PT60M' are one
298
+ -- elapsed time but two distinct strings. The one rule that acts on such a class,
299
+ -- predicate inference through equivalences, reads them off the Filter's SOURCE, and
300
+ -- adjacent filters merge into one Filter before it runs — so no spelling below
301
+ -- returned a wrong row even before the gate. These cases exist so the path stays
302
+ -- closed if filter merging or CTE property propagation changes.
303
+ -- Regression for `debt-filter-equality-facts-ignore-semantic-ordering`.
304
+
305
+ create table fso (id integer primary key, d timespan, s text);
306
+ insert into fso values (1, 'PT1H', 'PT60M'), (2, 'PT30M', 'PT45M');
307
+
308
+ -- Baseline: one Filter, both conjuncts together.
309
+ select id from fso where d = s and d = 'PT1H';
310
+ → [{"id":1}]
311
+
312
+ -- Nested sub-select: the inner filter carries the cross-column conjunct, the outer
313
+ -- one the constant pin, so they are two separate Filters.
314
+ select id from (select id, d, s from fso where d = s) where d = 'PT1H';
315
+ → [{"id":1}]
316
+
317
+ -- Same shape via a CTE.
318
+ with paired as (select id, d, s from fso where d = s)
319
+ select id from paired where d = 'PT1H';
320
+ → [{"id":1}]
321
+
322
+ -- Pinning the TEXT side instead: the pin is compared as text, so only the stored
323
+ -- spelling matches. Same intended asymmetry as the join cases above.
324
+ select id from (select id, d, s from fso where d = s) where s = 'PT1H';
325
+ → []
326
+
327
+ select id from (select id, d, s from fso where d = s) where s = 'PT60M';
328
+ → [{"id":1}]
329
+
330
+ drop table fso;
331
+
332
+ -- Same-type control: `timespan = timespan` keeps its mirror FDs and equivalence
333
+ -- class (the gate must not over-decline), and the row still comes back carrying its
334
+ -- own stored spelling — a canonicalizing "fix" would have rewritten these.
335
+ create table fsc (id integer primary key, d timespan, e timespan);
336
+ insert into fsc values (1, 'PT1H', 'PT60M'), (2, 'PT30M', 'PT45M');
337
+
338
+ select id, d, e from fsc where d = e and d = 'PT1H';
339
+ → [{"id":1,"d":"PT1H","e":"PT60M"}]
340
+
341
+ select id, d, e from (select id, d, e from fsc where d = e) where d = 'PT1H';
342
+ → [{"id":1,"d":"PT1H","e":"PT60M"}]
343
+
344
+ with paired as (select id, d, e from fsc where d = e)
345
+ select id, d, e from paired where e = 'PT60M';
346
+ → [{"id":1,"d":"PT1H","e":"PT60M"}]
347
+
348
+ drop table fsc;
349
+
350
+ -- === CHECK-derived cross-column equality facts ===
351
+ --
352
+ -- A declared `check (d = s)` used to mint the same mirror FDs and equivalence
353
+ -- class ungated, onto the TABLE REFERENCE — where `rule-predicate-inference-
354
+ -- equivalence` reads it directly, with nothing to make it dormant. The claim is
355
+ -- false for a mixed pair: the CHECK compares `d` and `s` by elapsed time, so a
356
+ -- row may legally store 'PT1H' in one column and 'PT60M' in the other.
357
+ -- Regression for `check-derived-equivalence-ignores-semantic-ordering`.
358
+ --
359
+ -- Every query below returned NO rows before the gate landed, except the
360
+ -- same-type control (which was and stays correct).
361
+
362
+ create table ckm (id integer primary key, d timespan, s text, check (d = s));
363
+ insert into ckm values (1, 'PT1H', 'PT60M');
364
+
365
+ -- The CHECK accepts the row: 'PT1H' and 'PT60M' are one elapsed time.
366
+ select id, d, s from ckm;
367
+ → [{"id":1,"d":"PT1H","s":"PT60M"}]
368
+
369
+ select id from ckm where d = 'PT1H';
370
+ → [{"id":1}]
371
+
372
+ drop table ckm;
373
+
374
+ -- Operand order reversed in the CHECK — the gate must be symmetric.
375
+ create table ckr (id integer primary key, d timespan, s text, check (s = d));
376
+ insert into ckr values (1, 'PT1H', 'PT60M');
377
+
378
+ select id from ckr where d = 'PT1H';
379
+ → [{"id":1}]
380
+
381
+ drop table ckr;
382
+
383
+ -- JSON against TEXT: '{"x":1}' and '{ "x" : 1 }' are one structure, two strings.
384
+ create table ckj (id integer primary key, a json, b text, check (a = b));
385
+ insert into ckj values (1, '{"x":1}', '{ "x" : 1 }');
386
+
387
+ select id from ckj where a = '{"x":1}';
388
+ → [{"id":1}]
389
+
390
+ drop table ckj;
391
+
392
+ -- Same-type control: `check (d = e)` over two TIMESPAN columns keeps its mirror
393
+ -- FDs and equivalence class (the gate must not over-decline), and the row comes
394
+ -- back carrying its own stored spellings — a canonicalizing "fix" would have
395
+ -- rewritten them.
396
+ create table ckt (id integer primary key, d timespan, e timespan, check (d = e));
397
+ insert into ckt values (1, 'PT1H', 'PT60M');
398
+
399
+ select id, d, e from ckt where d = 'PT1H';
400
+ → [{"id":1,"d":"PT1H","e":"PT60M"}]
401
+
402
+ select id, d, e from ckt where e = 'PT60M';
403
+ → [{"id":1,"d":"PT1H","e":"PT60M"}]
404
+
405
+ drop table ckt;
406
+
407
+ -- A partial UNIQUE index in play. Row 2 is outside the index's `where` scope, so
408
+ -- both rows may carry v = 7. The false {d, s} class dropped row 2 by inferring
409
+ -- `s = 'PT60M'` from the pin on `d`. (It also fed the guard-discharge route,
410
+ -- where an equivalence peer pinned to a literal discharges the partial index's
411
+ -- `where` clause and activates a uniqueness FD that does not hold; the two could
412
+ -- not be isolated from each other and close together.)
413
+ create table ckp (id integer primary key, d timespan, s text, v integer, check (d = s));
414
+ create unique index ckp_v on ckp (v) where s = 'PT60M';
415
+ insert into ckp values (1, 'PT60M', 'PT60M', 7);
416
+ insert into ckp values (2, 'PT1H', 'PT1H', 7);
417
+
418
+ -- No `order by` here on purpose: adding one changes the plan enough to hide the
419
+ -- defect (it returned both rows even ungated), so the bare form is the case that
420
+ -- discriminates.
421
+ select id from ckp where d = 'PT60M' and v = 7;
422
+ → [{"id":1},{"id":2}]
423
+
424
+ drop table ckp;
425
+
426
+ -- The hoisted-assertion route reaches the same extractor: a per-table assertion
427
+ -- `not exists (select 1 from t where d <> s)` is negated into a synthetic per-row
428
+ -- `d = s` check, and its facts merge onto the same TableReferenceNode. Same gate,
429
+ -- same outcome.
430
+ create table cka (id integer primary key, d timespan, s text);
431
+ create assertion cka_eq check (not exists (select 1 from cka where d <> s));
432
+ insert into cka values (1, 'PT1H', 'PT60M');
433
+
434
+ select id from cka where d = 'PT1H';
435
+ → [{"id":1}]
436
+
437
+ drop assertion cka_eq;
438
+ drop table cka;
439
+
440
+ -- USING over a mixed pair: `using (d)` means `l.d = r.d`, so the generic join's
441
+ -- USING comparison routes through the same comparator the operator does.
442
+ create table uma (id integer primary key, d timespan);
443
+ create table umb (d text, tag text, primary key (d, tag));
444
+ insert into uma values (1, 'PT1H');
445
+ insert into umb values ('PT60M', 'y'), ('PT45M', 'n');
446
+
447
+ select tag from umb join uma using (d);
448
+ → [{"tag":"y"}]
449
+
450
+ select tag from uma join umb using (d);
451
+ → [{"tag":"y"}]
452
+
453
+ drop table umb;
454
+ drop table uma;
455
+
456
+ -- A declined mixed pair lands on the nested-loop USING emitter, whose comparator
457
+ -- ranks NULL/NULL as EQUAL (it is an ordering function). Without the emitter's own
458
+ -- NULL guard this join would report a match that neither `=` nor the hash-keyed
459
+ -- path reports. The `on` form is the oracle.
460
+ create table una (id integer primary key, d timespan null);
461
+ create table unb (id integer primary key, d text null);
462
+ insert into una values (1, null), (2, 'PT1H');
463
+ insert into unb values (10, null), (20, 'PT60M');
464
+
465
+ select una.id as l, unb.id as r from una join unb using (d);
466
+ → [{"l":2,"r":20}]
467
+
468
+ select una.id as l, unb.id as r from una join unb on una.d = unb.d;
469
+ → [{"l":2,"r":20}]
470
+
471
+ drop table unb;
472
+ drop table una;
473
+
474
+ -- Control: when BOTH sides declare TIMESPAN the pair IS admissible and keeps the
475
+ -- physical (hash/merge) key path. These must stay green — a gate that
476
+ -- over-declines would still answer correctly here but via nested loop, while a
477
+ -- gate that broke the same-type path would drop the rows outright.
478
+ create table sma (id integer primary key, d timespan);
479
+ create table smb (id integer primary key, d timespan);
480
+ insert into sma values (1, 'PT1H'), (2, 'PT30M');
481
+ insert into smb values (10, 'PT60M'), (20, 'PT45M');
482
+
483
+ select sma.id as l, smb.id as r from sma join smb on sma.d = smb.d;
484
+ → [{"l":1,"r":10}]
485
+
486
+ select sma.id as l, smb.id as r from sma join smb using (d);
487
+ → [{"l":1,"r":10}]
488
+
489
+ drop table smb;
490
+ drop table sma;
491
+
492
+ -- JSON ↔ TEXT counterpart. These already passed before the gate landed, for a
493
+ -- structural reason worth pinning: `insertCrossTypeCoercion` wraps the text side in
494
+ -- `cast(… as json)`, so the ON operand is a CastNode rather than a bare column
495
+ -- reference and was never recognized as an equi-pair in the first place. If that
496
+ -- coercion ever stops firing, the semantic-ordering gate is what must catch the
497
+ -- pair instead — and these assertions are what notice.
498
+ create table jma (id integer primary key, doc json);
499
+ create table jmb (id integer primary key, s text);
500
+ insert into jma values (1, '{"a":1}');
501
+ insert into jmb values (10, '{ "a" : 1 }'), (20, '{"a":2}');
502
+
503
+ select jma.id as l, jmb.id as r from jma cross join jmb where jma.doc = jmb.s;
504
+ → [{"l":1,"r":10}]
505
+
506
+ select jma.id as l, jmb.id as r from jma join jmb on jma.doc = jmb.s;
507
+ → [{"l":1,"r":10}]
508
+
509
+ -- No JSON ↔ TEXT `using` case here on purpose: USING skips cross-type coercion
510
+ -- entirely, so the two sides still compare as OBJECT vs TEXT storage classes and
511
+ -- match nothing. Tracked as `bug-using-join-skips-cross-type-coercion`.
512
+
513
+ drop table jmb;
514
+ drop table jma;
515
+
516
+ -- === nullif / greatest / least route through declared semantic ordering ===
517
+ --
518
+ -- The three comparison builtins resolve their operand group's declared type and
519
+ -- collation at plan/emit time, so they agree with `=` and ORDER BY on TIMESPAN
520
+ -- and JSON columns. Regression for `nullif-greatest-least-comparison-seam`
521
+ -- (previously they compared raw bytes/text).
522
+
523
+ create table gnl (id integer primary key, d timespan, e timespan);
524
+ insert into gnl values (1, 'PT2H', 'PT90M');
525
+
526
+ -- nullif matches whenever `=` matches: PT120M ≡ PT2H by elapsed time.
527
+ select (d = 'PT120M') as r from gnl;
528
+ → [{"r":true}]
529
+
530
+ select nullif(d, 'PT120M') as r from gnl;
531
+ → [{"r":null}]
532
+
533
+ -- A different elapsed time still comes through unchanged.
534
+ select nullif(d, 'PT119M') as r from gnl;
535
+ → [{"r":"PT2H"}]
536
+
537
+ -- greatest/least rank by elapsed time (text order says 'PT2H' < 'PT90M'). The
538
+ -- durations are semantically distinct, so no tie-representative is pinned.
539
+ select greatest(d, e) as g, least(d, e) as l from gnl;
540
+ → [{"g":"PT2H","l":"PT90M"}]
541
+
542
+ -- A MIXED group — TIMESPAN column against a bare TEXT literal — still ranks by
543
+ -- elapsed time: the group comparator falls through to the runtime duration
544
+ -- check, the same route `d > 'PT30M'` takes. Plain text order says
545
+ -- 'PT2H' < 'PT30M' and would flip both answers.
546
+ select (d > 'PT30M') as r from gnl;
547
+ → [{"r":true}]
548
+
549
+ select greatest(d, 'PT30M') as g, least(d, 'PT30M') as l from gnl;
550
+ → [{"g":"PT2H","l":"PT30M"}]
551
+
552
+ -- Three-operand mixed group, so the fold takes the duration route repeatedly.
553
+ select greatest(d, e, 'PT30M') as g, least(d, e, 'PT30M') as l from gnl;
554
+ → [{"g":"PT2H","l":"PT30M"}]
555
+
556
+ -- NULL handling is the plain fold's, unchanged by the comparator swap: both
557
+ -- greatest and least skip NULLs.
558
+ select greatest(d, null) as g, least(d, null) as l, least(null, d) as l2 from gnl;
559
+ → [{"g":"PT2H","l":"PT2H","l2":"PT2H"}]
560
+
561
+ select nullif(d, null) as a, nullif(null, d) as b from gnl;
562
+ → [{"a":"PT2H","b":null}]
563
+
564
+ drop table gnl;
565
+
566
+ -- JSON: a textual operand is converted to the JSON side for the COMPARISON (the
567
+ -- same conversion `=`, IN and simple CASE apply through their synthetic cast), so
568
+ -- the comparison is structural — whitespace and key order are insignificant.
569
+ -- `nullif`/`greatest`/`least` return one of their arguments, so they convert only
570
+ -- a copy: the value handed back is the argument as written, never the parsed
571
+ -- document the comparison was run over.
572
+ create table jnl (id integer primary key, doc json, doc2 json);
573
+ insert into jnl values (1, '{"a":1}', '{ "a" : 1 }');
574
+
575
+ select (doc = '{ "a" : 1 }') as r from jnl;
576
+ → [{"r":true}]
577
+
578
+ -- Both sides JSON columns: structural before this ticket already.
579
+ select nullif(doc, doc2) as r from jnl;
580
+ → [{"r":null}]
581
+
582
+ -- One side a text literal: converting it for the comparison makes that structural
583
+ -- too, so the whitespace-different spelling matches.
584
+ select nullif(doc, '{ "a" : 1 }') as r from jnl;
585
+ → [{"r":null}]
586
+
587
+ -- A structurally different document still comes through — as the column's own
588
+ -- parsed value, since the column is the returned argument here.
589
+ select nullif(doc, '{"a":2}') as r from jnl;
590
+ → [{"r":{"a":1}}]
591
+
592
+ -- Flipped orientation: the TEXT literal is the group's probe, so it is the side
593
+ -- that converts. The comparison stays structural (whitespace-insensitive), and a
594
+ -- non-match returns the literal AS WRITTEN — the text, not the parsed document.
595
+ select nullif('{ "a" : 1 }', doc) as r from jnl;
596
+ → [{"r":null}]
597
+
598
+ select nullif('{ "a" : 2 }', doc) as r, typeof(nullif('{ "a" : 2 }', doc)) as t from jnl;
599
+ → [{"r":"{ \"a\" : 2 }","t":"text"}]
600
+
601
+ -- greatest/least over a JSON group rank structurally, not by text spelling — and
602
+ -- each returns its winning argument untouched: the text literal wins `greatest`
603
+ -- and comes back as text, while the column wins `least` and comes back parsed.
604
+ select greatest(doc, '{"a":2}') as g, least(doc, '{"a":2}') as l from jnl;
605
+ → [{"g":"{\"a\":2}","l":{"a":1}}]
606
+
607
+ drop table jnl;
608
+
609
+ -- Negative control: bare text literals declare TEXT, not TIMESPAN — text
610
+ -- ordering is correct for them ('PT2H' vs 'PT120M' are two distinct strings,
611
+ -- and 'PT90M' > 'PT2H' as text).
612
+ select nullif('PT2H', 'PT120M') as r;
613
+ → [{"r":"PT2H"}]
614
+
615
+ select greatest('PT2H', 'PT90M') as g, least('PT2H', 'PT90M') as l;
616
+ → [{"g":"PT90M","l":"PT2H"}]
617
+
618
+ -- === TIMESPAN UNIQUE enforcement: identity, not spelling ===
619
+ --
620
+ -- A UNIQUE constraint over a semantic-ordering column compares candidates through
621
+ -- the column's declared type, so an equal-elapsed re-spelling is a duplicate — the
622
+ -- same identity `=`, DISTINCT and the PRIMARY KEY already use above. Runs under both
623
+ -- the memory module (`yarn test`) and the store module (`yarn test:store`); both must
624
+ -- agree. (Regression: memory's candidate lookup was typed but its re-validation
625
+ -- compared raw text, so the duplicate was found and then discarded as unequal.)
626
+
627
+ create table u (id integer primary key, d timespan unique);
628
+ insert into u values (1, 'PT1H');
629
+
630
+ insert into u values (2, 'PT60M');
631
+ -- error: UNIQUE constraint failed
632
+
633
+ -- `on conflict ignore` drops the duplicate spelling; the original row survives.
634
+ insert or ignore into u values (3, 'PT60M');
635
+ select id, timespan_total_seconds(d) as s from u;
636
+ → [{"id":1,"s":3600}]
637
+
638
+ -- `on conflict replace` evicts the row the conflicting spelling names.
639
+ insert or replace into u values (4, 'PT1H0S');
640
+ select id, timespan_total_seconds(d) as s from u;
641
+ → [{"id":4,"s":3600}]
642
+
643
+ drop table u;
644
+
645
+ -- Composite UNIQUE: the TIMESPAN member collapses spellings, a differing leading
646
+ -- column is still a distinct key.
647
+ create table uc (id integer primary key, k integer, d timespan, unique (k, d));
648
+ insert into uc values (1, 1, 'PT1H');
649
+
650
+ insert into uc values (2, 1, 'PT60M');
651
+ -- error: UNIQUE constraint failed
652
+
653
+ insert into uc values (3, 2, 'PT60M');
654
+ select count(*) as c from uc;
655
+ → [{"c":2}]
656
+
657
+ drop table uc;
658
+
659
+ -- The index-derived variant of the same write-time identity check lives in
660
+ -- 15.1.2-semantic-ordering-index (it needs standalone CREATE UNIQUE INDEX).
661
+
662
+ -- UPDATE: re-spelling a row's OWN value is not a self-conflict; moving a row onto
663
+ -- ANOTHER row's identity is.
664
+ create table uu (id integer primary key, d timespan unique);
665
+ insert into uu values (1, 'PT1H'), (2, 'PT30M');
666
+
667
+ update uu set d = 'PT60M' where id = 1;
668
+ select id, timespan_total_seconds(d) as s from uu order by id;
669
+ → [{"id":1,"s":3600},{"id":2,"s":1800}]
670
+
671
+ update uu set d = 'PT60M' where id = 2;
672
+ -- error: UNIQUE constraint failed
673
+
674
+ select count(*) as c from uu;
675
+ → [{"c":2}]
676
+
677
+ drop table uu;
678
+
679
+ -- Negative control: the SAME strings in a declared TEXT column stay two distinct
680
+ -- values — only a declared semantic-ordering type changes UNIQUE identity.
681
+ create table ut (id integer primary key, d text unique);
682
+ insert into ut values (1, 'PT1H'), (2, 'PT60M');
683
+ select count(*) as c from ut;
684
+ → [{"c":2}]
685
+
686
+ drop table ut;
687
+
688
+ -- An explicit `on conflict (<col>)` target routes on the SAME identity the
689
+ -- constraint enforces: naming a semantic-ordering column does not narrow the match
690
+ -- back to byte equality. (Regression: the executor decided which clause a violation
691
+ -- belonged to by comparing the target columns under storage class + collation only,
692
+ -- so 'PT1H' vs 'PT60M' matched no clause and the UNIQUE error was re-raised.)
693
+ -- NOTE: these assert through timespan_total_seconds, like the rest of this file, so
694
+ -- they pin the identity but not WHICH spelling of d survives a DO UPDATE (the
695
+ -- existing row's, since d is unassigned). If a backend is ever changed to adopt the
696
+ -- proposed row's key spelling, add a raw `select d` here to pin it.
697
+ create table ux (id integer primary key, d timespan unique, n integer);
698
+ insert into ux values (1, 'PT1H', 10);
699
+
700
+ insert into ux values (2, 'PT60M', 20) on conflict (d) do update set n = 99;
701
+ select id, timespan_total_seconds(d) as s, n from ux;
702
+ → [{"id":1,"s":3600,"n":99}]
703
+
704
+ insert into ux values (4, 'PT60M', 40) on conflict (d) do nothing;
705
+ select id, timespan_total_seconds(d) as s, n from ux;
706
+ → [{"id":1,"s":3600,"n":99}]
707
+
708
+ -- The untargeted form matches any constraint and never compares values — unchanged.
709
+ insert into ux values (5, 'PT3600S', 50) on conflict do update set n = 77;
710
+ select id, timespan_total_seconds(d) as s, n from ux;
711
+ → [{"id":1,"s":3600,"n":77}]
712
+
713
+ -- A conflict on a DIFFERENT constraint than the target still aborts: the target
714
+ -- columns disagree, so no clause covers it.
715
+ insert into ux values (1, 'PT5M', 60) on conflict (d) do update set n = 1;
716
+ -- error: constraint failed
717
+
718
+ drop table ux;
719
+
720
+ -- Composite target with a TIMESPAN member: the re-spelled member still names the
721
+ -- same key, a differing leading column is a distinct key (plain insert).
722
+ create table uxc (id integer primary key, k integer, d timespan, n integer, unique (k, d));
723
+ insert into uxc values (1, 1, 'PT1H', 10);
724
+
725
+ insert into uxc values (2, 1, 'PT60M', 20) on conflict (k, d) do update set n = 99;
726
+ insert into uxc values (3, 2, 'PT60M', 30) on conflict (k, d) do update set n = 88;
727
+ select id, k, timespan_total_seconds(d) as s, n from uxc order by id;
728
+ → [{"id":1,"k":1,"s":3600,"n":99},{"id":3,"k":2,"s":3600,"n":30}]
729
+
730
+ drop table uxc;
731
+
732
+ -- A PRIMARY KEY target resolves its enforcement collation down the other branch
733
+ -- (the PK column definition, not a UNIQUE constraint) — the semantic identity has
734
+ -- to survive that branch too. `excluded.` values reach the assignment normally.
735
+ create table uxp (d timespan primary key, n integer);
736
+ insert into uxp values ('PT1H', 10);
737
+
738
+ insert into uxp values ('PT60M', 20) on conflict (d) do update set n = excluded.n;
739
+ select timespan_total_seconds(d) as s, n from uxp;
740
+ → [{"s":3600,"n":20}]
741
+
742
+ insert into uxp values ('PT90M', 30) on conflict (d) do update set n = excluded.n;
743
+ select timespan_total_seconds(d) as s, n from uxp order by s;
744
+ → [{"s":3600,"n":20},{"s":5400,"n":30}]
745
+
746
+ drop table uxp;
747
+
748
+ -- JSON is the other semantic-ordering type. Its values reach the executor already
749
+ -- canonicalized, so both sides of the match were equal even before this fix; pinned
750
+ -- so the routing stays correct whichever layer canonicalizes.
751
+ create table uxj (id integer primary key, v json unique, n integer);
752
+ insert into uxj values (1, json('{"a":1,"b":2}'), 10);
753
+
754
+ insert into uxj values (2, json('{"b":2,"a":1}'), 20) on conflict (v) do update set n = 99;
755
+ select id, n from uxj order by id;
756
+ → [{"id":1,"n":99}]
757
+
758
+ drop table uxj;
759
+
760
+ -- Negative control: a TEXT conflict target keeps text identity — a differently
761
+ -- spelled duration is a new row, and the same spelling still routes to DO UPDATE.
762
+ create table uxt (id integer primary key, d text unique, n integer);
763
+ insert into uxt values (1, 'PT1H', 10);
764
+
765
+ insert into uxt values (2, 'PT60M', 20) on conflict (d) do update set n = 99;
766
+ insert into uxt values (3, 'PT1H', 30) on conflict (d) do update set n = 55;
767
+ select id, d, n from uxt order by id;
768
+ → [{"id":1,"d":"PT1H","n":55},{"id":2,"d":"PT60M","n":20}]
769
+
770
+ drop table uxt;
771
+
772
+ -- === Untyped expressions keep storage-class/text ordering ===
773
+
774
+ -- Text literals (no declared TIMESPAN type): DISTINCT keeps both spellings.
775
+ select count(*) as c from (
776
+ select distinct d from (select 'PT1H' as d union all select 'PT60M' as d) as u
777
+ ) as q;
778
+ → [{"c":2}]
779
+
780
+ -- ANY column holding duration-shaped strings orders as text ('PT2H' < 'PT90M').
781
+ create table anyt (id integer primary key, v any);
782
+ insert into anyt values (1, 'PT2H'), (2, 'PT90M');
783
+ select id from anyt order by v;
784
+ → [{"id":1},{"id":2}]
785
+
786
+ drop table anyt;
787
+
788
+ -- A plain TEXT column holding duration-shaped text stays text-ordered, and BETWEEN
789
+ -- must agree with the comparisons it desugars to. (Regression: a runtime duration
790
+ -- probe applied unconditionally in BETWEEN made these two disagree — 'PT30M' passed
791
+ -- the text comparisons but was rejected by BETWEEN.)
792
+ create table txt (id integer primary key, v text);
793
+ insert into txt values (1, 'PT30M'), (2, 'PT2H'), (3, 'PT90M');
794
+
795
+ select id from txt where v between 'PT100M' and 'PT3H' order by id;
796
+ → [{"id":1},{"id":2}]
797
+
798
+ select id from txt where v >= 'PT100M' and v <= 'PT3H' order by id;
799
+ → [{"id":1},{"id":2}]
800
+
801
+ -- A simple CASE over the same plain TEXT column stays text-compared too: 'PT30M'
802
+ -- and 'PT0.5H' are one elapsed time but two distinct strings, and only a declared
803
+ -- semantic-ordering type changes that. Must agree with `=`.
804
+ select id, (v = 'PT0.5H') as eq, case v when 'PT0.5H' then 'hit' else 'miss' end as c
805
+ from txt where id = 1;
806
+ → [{"id":1,"eq":false,"c":"miss"}]
807
+
808
+ -- The exact spelling still matches under text comparison.
809
+ select id from txt where case v when 'PT30M' then 1 else 0 end = 1;
810
+ → [{"id":1}]
811
+
812
+ drop table txt;
813
+
814
+ -- === JSON: structural ordering ===
815
+
816
+ create table jv (id integer primary key, v json);
817
+ insert into jv values (1, '{"a":10}'), (2, '{"a":2}');
818
+
819
+ -- Structural order: {"a":2} < {"a":10} because 2 < 10 numerically (canonical-text
820
+ -- order would sort '{"a":10}' first).
821
+ select id from jv order by v;
822
+ → [{"id":2},{"id":1}]
823
+
824
+ select id from jv order by v desc;
825
+ → [{"id":1},{"id":2}]
826
+
827
+ -- Comparison operators use the structural order too.
828
+ select (json('{"a":2}') < json('{"a":10}')) as r;
829
+ → [{"r":true}]
830
+
831
+ -- JSON type-rank order: number < string < array < object.
832
+ select (json('5') < json('"a"')) as r;
833
+ → [{"r":true}]
834
+
835
+ select (json('[99]') < json('{"a":0}')) as r;
836
+ → [{"r":true}]
837
+
838
+ -- Equality is unchanged: key order in objects is insignificant either way.
839
+ select (json('{"a":1,"b":2}') = json('{"b":2,"a":1}')) as r;
840
+ → [{"r":true}]
841
+
842
+ -- BETWEEN over a JSON column agrees with the structural comparisons it desugars to.
843
+ select id from jv where v between json('{"a":1}') and json('{"a":5}');
844
+ → [{"id":2}]
845
+
846
+ -- Simple CASE over a JSON column compares structurally (whitespace and key order
847
+ -- are insignificant), unchanged by the collation/type routing rework: the plan-time
848
+ -- cast the builder inserts on the WHEN operands already put both sides in JSON.
849
+ select id, case v when '{ "a" : 2 }' then 'hit' else 'miss' end as r from jv order by id;
850
+ → [{"id":1,"r":"miss"},{"id":2,"r":"hit"}]
851
+
852
+ -- JSON row identity: key order is insignificant, so two reordered spellings of one
853
+ -- document are one value to DISTINCT, GROUP BY and set operations. (JSON needs no
854
+ -- groupKey hook — its canonical text form is already identity-faithful — so these
855
+ -- assert that the canonical form, not the stored insertion order, drives the key.)
856
+ create table jd (id integer primary key, v json);
857
+ insert into jd values (1, '{"a":1,"b":2}'), (2, '{"b":2,"a":1}');
858
+
859
+ select count(*) as c from (select distinct v from jd) as q;
860
+ → [{"c":1}]
861
+
862
+ select count(*) as c from (select v from jd group by v) as q;
863
+ → [{"c":1}]
864
+
865
+ select count(*) as c from (
866
+ select v from jd where id = 1
867
+ union
868
+ select v from jd where id = 2
869
+ ) as q;
870
+ → [{"c":1}]
871
+
872
+ -- Window PARTITION BY and an equi-join key collapse the same identity.
873
+ select count(*) over (partition by v) as c from jd;
874
+ → [{"c":2},{"c":2}]
875
+
876
+ create table jd2 (id integer primary key, v json);
877
+ insert into jd2 values (10, '{"b":2,"a":1}');
878
+ select jd.id as l, jd2.id as r from jd join jd2 on jd.v = jd2.v order by l;
879
+ → [{"l":1,"r":10},{"l":2,"r":10}]
880
+
881
+ drop table jd2;
882
+ drop table jd;
883
+
884
+ drop table jv;
885
+
886
+ -- === Window min()/max() follow the same rule as the aggregate ===
887
+ --
888
+ -- Window functions resolve through their own registry (schema/window-function.ts),
889
+ -- so the emitter binds each call site's argument type + collation to the MIN/MAX
890
+ -- step exactly as the aggregate does. `min(x) over (…)` must therefore equal
891
+ -- `min(x)` over the same rows, and agree with `order by x limit 1`.
892
+
893
+ create table wts (id integer primary key, dur timespan);
894
+ insert into wts values (1,'PT30M'), (2,'PT1H'), (3,'PT2H'), (4,'P1D'), (5,'PT0S');
895
+
896
+ -- Whole-partition frame: elapsed-time order. Text order would give mn=P1D, mx=PT30M.
897
+ select distinct min(dur) over () as mn, max(dur) over () as mx from wts;
898
+ → [{"mn":"PT0S","mx":"P1D"}]
899
+
900
+ -- The window form agrees with the aggregate form and with order by … limit 1.
901
+ select distinct
902
+ (min(dur) over () = (select min(dur) from wts)) as min_eq_agg,
903
+ (min(dur) over () = (select dur from wts order by dur limit 1)) as min_eq_order,
904
+ (max(dur) over () = (select max(dur) from wts)) as max_eq_agg,
905
+ (max(dur) over () = (select dur from wts order by dur desc limit 1)) as max_eq_order
906
+ from wts;
907
+ → [{"min_eq_agg":true,"min_eq_order":true,"max_eq_agg":true,"max_eq_order":true}]
908
+
909
+ -- Running frame (unbounded preceding): tightens by elapsed time, so 'P1D' at id=4
910
+ -- never becomes the minimum and 'PT0S' at id=5 does.
911
+ select id, min(dur) over (order by id rows unbounded preceding) as rmn from wts order by id;
912
+ → [{"id":1,"rmn":"PT30M"},{"id":2,"rmn":"PT30M"},{"id":3,"rmn":"PT30M"},{"id":4,"rmn":"PT30M"},{"id":5,"rmn":"PT0S"}]
913
+
914
+ -- Sliding frame (1 preceding .. 1 following): each window ranks independently.
915
+ select id, min(dur) over (order by id rows between 1 preceding and 1 following) as mn,
916
+ max(dur) over (order by id rows between 1 preceding and 1 following) as mx
917
+ from wts order by id;
918
+ → [{"id":1,"mn":"PT30M","mx":"PT1H"},{"id":2,"mn":"PT30M","mx":"PT2H"},{"id":3,"mn":"PT1H","mx":"P1D"},{"id":4,"mn":"PT0S","mx":"P1D"},{"id":5,"mn":"PT0S","mx":"P1D"}]
919
+
920
+ drop table wts;
921
+
922
+ -- Mixed storage classes in an ANY column. A raw JavaScript `<` never selects the
923
+ -- number (both `5 < 'abc'` and `'abc' < 5` are false), so the window minimum used
924
+ -- to be whichever non-null value arrived first.
925
+ create table wany (id integer primary key, v any);
926
+ insert into wany values (1,'abc'), (2,5), (3,'zz');
927
+
928
+ select v from wany order by v;
929
+ → [{"v":5},{"v":"abc"},{"v":"zz"}]
930
+
931
+ select distinct min(v) over () as mn, max(v) over () as mx from wany;
932
+ → [{"mn":5,"mx":"zz"}]
933
+
934
+ drop table wany;
935
+
936
+ -- Declared collation: NOCASE ranks 'a' below 'B'; BINARY would report mn=B, mx=a.
937
+ create table wnc (id integer primary key, t text collate NOCASE);
938
+ insert into wnc values (1,'B'), (2,'a'), (3,'c');
939
+
940
+ select distinct min(t) over () as mn, max(t) over () as mx from wnc;
941
+ → [{"mn":"a","mx":"c"}]
942
+
943
+ drop table wnc;
944
+
945
+ -- Blobs compare bytewise, agreeing with the aggregate and with ORDER BY. Under a
946
+ -- raw `<` two Uint8Arrays coerce to strings, which is neither byte order nor
947
+ -- anything else meaningful.
948
+ create table wbl (id integer primary key, b blob);
949
+ insert into wbl values (1, x'ff00'), (2, x'0102'), (3, x'80');
950
+
951
+ select id from wbl order by b;
952
+ → [{"id":2},{"id":3},{"id":1}]
953
+
954
+ select distinct
955
+ (min(b) over () = (select min(b) from wbl)) as min_eq_agg,
956
+ (max(b) over () = (select max(b) from wbl)) as max_eq_agg
957
+ from wbl;
958
+ → [{"min_eq_agg":true,"max_eq_agg":true}]
959
+
960
+ drop table wbl;