@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,267 @@
1
+ -- adapted from SQLite test/join.test, joinB.test, joinH.test
2
+ -- USING clause: Quereus parses USING as an equi-join shortcut but does NOT merge
3
+ -- the shared column into a single output projection (SQLite-specific semantics).
4
+ -- Tests qualify all column references; an unqualified shared name is ambiguous.
5
+
6
+ create table u_a (id integer primary key, k integer, va text);
7
+ create table u_b (id integer primary key, k integer, vb text);
8
+
9
+ insert into u_a values (1, 100, 'a1'), (2, 200, 'a2'), (3, 300, 'a3');
10
+ insert into u_b values (10, 100, 'b1'), (20, 200, 'b2'), (40, 999, 'b4');
11
+
12
+ -- Single-column USING: filters on shared key; both tables' columns remain selectable
13
+ select u_a.id as aid, u_a.k as ak, va, u_b.id as bid, u_b.k as bk, vb from u_a join u_b using (k) order by u_a.id;
14
+ → [{"aid":1,"ak":100,"va":"a1","bid":10,"bk":100,"vb":"b1"},{"aid":2,"ak":200,"va":"a2","bid":20,"bk":200,"vb":"b2"}]
15
+
16
+ -- Unqualified shared-key reference is ambiguous (SQLite would merge USING column;
17
+ -- Quereus does not).
18
+ select k, va, vb from u_a join u_b using (k) order by u_a.id;
19
+ -- error: ambiguous column name: k
20
+
21
+ -- select * over a USING join exposes both sides' columns with `:N` disambiguation
22
+ -- (matches the same shape as `inner join ... on u_a.k = u_b.k`).
23
+ select * from u_a join u_b using (k) order by u_a.id;
24
+ → [{"id":1,"k":100,"va":"a1","id:1":10,"k:1":100,"vb":"b1"},{"id":2,"k":200,"va":"a2","id:1":20,"k:1":200,"vb":"b2"}]
25
+
26
+ -- LEFT JOIN with USING preserves left rows; right side is null on miss
27
+ select u_a.id as aid, u_a.k as ak, u_b.id as bid, vb from u_a left join u_b using (k) order by u_a.id;
28
+ → [{"aid":1,"ak":100,"bid":10,"vb":"b1"},{"aid":2,"ak":200,"bid":20,"vb":"b2"},{"aid":3,"ak":300,"bid":null,"vb":null}]
29
+
30
+ -- select * over a LEFT JOIN ... USING also disambiguates and keeps unmatched right side as nulls.
31
+ select * from u_a left join u_b using (k) order by u_a.id;
32
+ → [{"id":1,"k":100,"va":"a1","id:1":10,"k:1":100,"vb":"b1"},{"id":2,"k":200,"va":"a2","id:1":20,"k:1":200,"vb":"b2"},{"id":3,"k":300,"va":"a3","id:1":null,"k:1":null,"vb":null}]
33
+
34
+ drop table u_a;
35
+ drop table u_b;
36
+ -- run
37
+
38
+ -- Multi-column USING (a, b): both keys must match
39
+ create table mu_l (a integer, b integer, vl text, primary key (a, b));
40
+ create table mu_r (a integer, b integer, vr text, primary key (a, b));
41
+
42
+ insert into mu_l values (1, 10, 'l1'), (2, 20, 'l2'), (3, 10, 'l3');
43
+ insert into mu_r values (1, 10, 'r1'), (2, 30, 'r2'), (3, 10, 'r3');
44
+
45
+ -- Inner USING join on (a, b) with qualified references
46
+ select mu_l.a as la, mu_l.b as lb, vl, vr from mu_l join mu_r using (a, b) order by mu_l.a, mu_l.b;
47
+ → [{"la":1,"lb":10,"vl":"l1","vr":"r1"},{"la":3,"lb":10,"vl":"l3","vr":"r3"}]
48
+
49
+ -- LEFT JOIN with multi-column USING
50
+ select mu_l.a as la, mu_l.b as lb, vl, vr from mu_l left join mu_r using (a, b) order by mu_l.a, mu_l.b;
51
+ → [{"la":1,"lb":10,"vl":"l1","vr":"r1"},{"la":2,"lb":20,"vl":"l2","vr":null},{"la":3,"lb":10,"vl":"l3","vr":"r3"}]
52
+
53
+ drop table mu_l;
54
+ drop table mu_r;
55
+ -- run
56
+
57
+ -- USING with TEXT keys uses default binary collation
58
+ create table cu_a (id integer primary key, name text);
59
+ create table cu_b (id integer primary key, name text);
60
+
61
+ insert into cu_a values (1, 'alice'), (2, 'BOB'), (3, 'carol');
62
+ insert into cu_b values (10, 'alice'), (20, 'bob'), (30, 'carol');
63
+
64
+ -- Default binary collation: 'BOB' != 'bob'
65
+ select cu_a.id as aid, cu_a.name as aname, cu_b.id as bid from cu_a join cu_b using (name) order by cu_a.id;
66
+ → [{"aid":1,"aname":"alice","bid":10},{"aid":3,"aname":"carol","bid":30}]
67
+
68
+ drop table cu_a;
69
+ drop table cu_b;
70
+
71
+ -- USING with COLLATE NOCASE TEXT keys folds case: the join honors the columns'
72
+ -- collation, so 'BOB' matches 'bob'
73
+ -- (unique-collation-name-case-insensitive-normalization).
74
+ create table cn_a (id integer primary key, name text collate nocase);
75
+ create table cn_b (id integer primary key, name text collate nocase);
76
+
77
+ insert into cn_a values (1, 'alice'), (2, 'BOB'), (3, 'carol');
78
+ insert into cn_b values (10, 'alice'), (20, 'bob'), (30, 'carol');
79
+
80
+ select cn_a.id as aid, cn_a.name as aname, cn_b.id as bid from cn_a join cn_b using (name) order by cn_a.id;
81
+ → [{"aid":1,"aname":"alice","bid":10},{"aid":2,"aname":"BOB","bid":20},{"aid":3,"aname":"carol","bid":30}]
82
+
83
+ drop table cn_a;
84
+ drop table cn_b;
85
+ -- run
86
+
87
+ -- ===== USING collation precedence (both spellings) + conflict =====
88
+ -- (ticket join-key-collation-resolution-alignment) A USING pair resolves its
89
+ -- comparison collation through the shared provenance lattice
90
+ -- (explicit > declared > default > BINARY), SYMMETRICALLY: a declared NOCASE on
91
+ -- EITHER side governs the join regardless of which side spells it, matching the
92
+ -- equivalent `l.name = r.name`. Fixtures are chosen so BINARY yields 0 matches
93
+ -- and NOCASE yields 3 — the row count alone pins which collation is in effect.
94
+
95
+ -- Left declares NOCASE, right defaults to BINARY → NOCASE wins (folds case).
96
+ create table up_l (id integer primary key, name text collate nocase);
97
+ create table up_r (id integer primary key, name text);
98
+ insert into up_l values (1, 'Alice'), (2, 'BOB'), (3, 'carol');
99
+ insert into up_r values (10, 'alice'), (20, 'bob'), (30, 'CAROL');
100
+ select up_l.id as lid, up_r.id as rid from up_l join up_r using (name) order by up_l.id;
101
+ → [{"lid":1,"rid":10},{"lid":2,"rid":20},{"lid":3,"rid":30}]
102
+
103
+ -- Mirror spelling: left defaults to BINARY, right declares NOCASE → still NOCASE.
104
+ -- The pre-alignment left-only rule (`leftType ?? BINARY`) would have compared
105
+ -- under BINARY here and matched nothing; the symmetric lattice folds case.
106
+ create table um_l (id integer primary key, name text);
107
+ create table um_r (id integer primary key, name text collate nocase);
108
+ insert into um_l values (1, 'Alice'), (2, 'BOB'), (3, 'carol');
109
+ insert into um_r values (10, 'alice'), (20, 'bob'), (30, 'CAROL');
110
+ select um_l.id as lid, um_r.id as rid from um_l join um_r using (name) order by um_l.id;
111
+ → [{"lid":1,"rid":10},{"lid":2,"rid":20},{"lid":3,"rid":30}]
112
+
113
+ drop table up_l;
114
+ drop table up_r;
115
+ drop table um_l;
116
+ drop table um_r;
117
+ -- run
118
+
119
+ -- Conflicting DECLARED collations on a USING pair → plan-time ambiguous-collation
120
+ -- error, raised at build: `using (name)` desugars to the `l.name = r.name`
121
+ -- BinaryOpNode, whose type generation reports the conflict. The spelled-out ON
122
+ -- form must error IDENTICALLY (uniformity between USING and ON).
123
+ create table cf_a (id integer primary key, name text collate nocase);
124
+ create table cf_b (id integer primary key, name text collate rtrim);
125
+ insert into cf_a values (1, 'bob');
126
+ insert into cf_b values (10, 'BOB');
127
+ -- run
128
+
129
+ select cf_a.id from cf_a join cf_b using (name);
130
+ -- error: ambiguous collation
131
+
132
+ select cf_a.id from cf_a join cf_b on cf_a.name = cf_b.name;
133
+ -- error: ambiguous collation
134
+
135
+ -- An explicit COLLATE on the comparison resolves the ambiguity (rank-3 explicit
136
+ -- outranks both declared collations), so the same join builds and runs. USING has
137
+ -- no per-column COLLATE syntax, so pin the resolution via the ON form: under the
138
+ -- explicit NOCASE, 'bob' matches 'BOB'.
139
+ select cf_a.id as aid, cf_b.id as bid from cf_a join cf_b on cf_a.name = cf_b.name collate nocase order by cf_a.id;
140
+ → [{"aid":1,"bid":10}]
141
+
142
+ drop table cf_a;
143
+ drop table cf_b;
144
+ -- run
145
+
146
+ -- ===== NULL USING keys never match =====
147
+ -- `using (k)` is `l.k = r.k`, and `null = null` is UNKNOWN, so two NULL keys do
148
+ -- NOT join — on either physical path. The nested-loop emitter compares through an
149
+ -- ORDERING comparator, which ranks NULL/NULL as equal, so it carries its own NULL
150
+ -- guard; the hash path drops NULL keys by construction. The two must agree with
151
+ -- each other and with the spelled-out ON form.
152
+ create table nu_l (id integer primary key, k text null);
153
+ create table nu_r (id integer primary key, k text null);
154
+ insert into nu_l values (1, null), (2, 'a');
155
+ insert into nu_r values (10, null), (20, 'a');
156
+
157
+ select nu_l.id as lid, nu_r.id as rid from nu_l join nu_r using (k) order by nu_l.id;
158
+ → [{"lid":2,"rid":20}]
159
+
160
+ select nu_l.id as lid, nu_r.id as rid from nu_l join nu_r on nu_l.k = nu_r.k order by nu_l.id;
161
+ → [{"lid":2,"rid":20}]
162
+
163
+ -- LEFT JOIN: the NULL-keyed left row survives, null-extended, rather than pairing
164
+ -- with the NULL-keyed right row.
165
+ select nu_l.id as lid, nu_r.id as rid from nu_l left join nu_r using (k) order by nu_l.id;
166
+ → [{"lid":1,"rid":null},{"lid":2,"rid":20}]
167
+
168
+ drop table nu_l;
169
+ drop table nu_r;
170
+ -- run
171
+
172
+ -- ===== Cross-type USING pairs coerce exactly as the ON form does =====
173
+ -- (ticket bug-using-join-skips-cross-type-coercion) `using (k)` desugars to
174
+ -- `l.k = r.k`, so `insertCrossTypeCoercion` reconciles the pair: a JSON column
175
+ -- against a TEXT one compares structurally (key order irrelevant), and an INTEGER
176
+ -- against a TEXT one compares numerically. Before the desugar USING compared raw
177
+ -- storage classes and returned 0 rows where the ON spelling returned the row.
178
+ create table xj_l (id integer primary key, k json);
179
+ create table xj_r (id integer primary key, k text);
180
+ insert into xj_l values (1, '{"a":1,"b":2}');
181
+ insert into xj_r values (10, '{"b":2,"a":1}');
182
+
183
+ select xj_l.id as lid, xj_r.id as rid from xj_l join xj_r using (k);
184
+ → [{"lid":1,"rid":10}]
185
+
186
+ select xj_l.id as lid, xj_r.id as rid from xj_l join xj_r on xj_l.k = xj_r.k;
187
+ → [{"lid":1,"rid":10}]
188
+
189
+ create table xn_l (id integer primary key, k integer);
190
+ create table xn_r (id integer primary key, k text);
191
+ insert into xn_l values (1, 5), (2, 6);
192
+ insert into xn_r values (10, '5'), (20, 'six');
193
+
194
+ select xn_l.id as lid, xn_r.id as rid from xn_l join xn_r using (k);
195
+ → [{"lid":1,"rid":10}]
196
+
197
+ select xn_l.id as lid, xn_r.id as rid from xn_l join xn_r on xn_l.k = xn_r.k;
198
+ → [{"lid":1,"rid":10}]
199
+
200
+ -- A cross-type USING pair runs as nested loop (the coerced operand is a CastNode, so
201
+ -- no equi-pair is extracted), which is the path an outer join's null-extension takes.
202
+ -- The unmatched left row must still survive, and the ON form must agree.
203
+ select xn_l.id as lid, xn_r.id as rid from xn_l left join xn_r using (k) order by xn_l.id;
204
+ → [{"lid":1,"rid":10},{"lid":2,"rid":null}]
205
+
206
+ select xn_l.id as lid, xn_r.id as rid from xn_l left join xn_r on xn_l.k = xn_r.k order by xn_l.id;
207
+ → [{"lid":1,"rid":10},{"lid":2,"rid":null}]
208
+
209
+ drop table xj_l;
210
+ drop table xj_r;
211
+ drop table xn_l;
212
+ drop table xn_r;
213
+ -- run
214
+
215
+ -- Multi-column USING where only ONE column is cross-type: the same-type column still
216
+ -- keys the join and the coerced one is evaluated as a residual predicate. Both must
217
+ -- apply — row (2,'7') differs on the coerced column and must not join.
218
+ create table xm_l (id integer primary key, a integer, b integer);
219
+ create table xm_r (id integer primary key, a integer, b text);
220
+ insert into xm_l values (1, 100, 5), (2, 200, 6);
221
+ insert into xm_r values (10, 100, '5'), (20, 200, '7');
222
+
223
+ select xm_l.id as lid, xm_r.id as rid from xm_l join xm_r using (a, b) order by xm_l.id;
224
+ → [{"lid":1,"rid":10}]
225
+
226
+ select xm_l.id as lid, xm_r.id as rid from xm_l join xm_r on xm_l.a = xm_r.a and xm_l.b = xm_r.b order by xm_l.id;
227
+ → [{"lid":1,"rid":10}]
228
+
229
+ drop table xm_l;
230
+ drop table xm_r;
231
+ -- run
232
+
233
+ -- ===== A USING column absent from a side is an error, not a silent empty result =====
234
+ -- Formerly the emitter's USING comparator returned false for an unresolved column
235
+ -- index, so the join produced zero rows and raised nothing.
236
+ create table mi_l (id integer primary key, k integer);
237
+ create table mi_r (id integer primary key, k integer);
238
+ insert into mi_l values (1, 10);
239
+ insert into mi_r values (10, 10);
240
+ -- run
241
+
242
+ select mi_l.id from mi_l join mi_r using (zzz);
243
+ -- error: USING column not found on left side of join: zzz
244
+
245
+ drop table mi_l;
246
+ drop table mi_r;
247
+ -- run
248
+
249
+ -- ===== Three-way USING: first-match-per-side pairing =====
250
+ -- The second join's left side (the a-b join) carries TWO `k` attributes. The pair
251
+ -- is formed from the FIRST match on each side, i.e. `t3a.k = t3c.k` — pinned by
252
+ -- making the first join a LEFT join so a null-extended row has `t3b.k` NULL while
253
+ -- `t3a.k` still holds 20. Pairing on `t3b.k` would drop that row.
254
+ create table t3a (id integer primary key, k integer);
255
+ create table t3b (id integer primary key, k integer);
256
+ create table t3c (id integer primary key, k integer);
257
+ insert into t3a values (1, 10), (2, 20);
258
+ insert into t3b values (11, 10);
259
+ insert into t3c values (111, 10), (222, 20);
260
+
261
+ select t3a.id as aid, t3b.id as bid, t3c.id as cid
262
+ from t3a left join t3b using (k) join t3c using (k) order by t3a.id;
263
+ → [{"aid":1,"bid":11,"cid":111},{"aid":2,"bid":null,"cid":222}]
264
+
265
+ drop table t3a;
266
+ drop table t3b;
267
+ drop table t3c;
@@ -0,0 +1,35 @@
1
+ -- adapted from SQLite test/join3.test, join.test
2
+ -- Quereus does NOT support SQLite's comma syntax in FROM (implicit cross join).
3
+ -- Use explicit JOIN instead. This file pins the rejection.
4
+
5
+ create table cj_a (id integer primary key, va text);
6
+ create table cj_b (id integer primary key, vb text);
7
+ create table cj_c (id integer primary key, vc text);
8
+
9
+ insert into cj_a values (1, 'a1'), (2, 'a2');
10
+ insert into cj_b values (10, 'b1'), (20, 'b2'), (30, 'b3');
11
+ insert into cj_c values (100, 'c1'), (200, 'c2');
12
+ -- run
13
+
14
+ -- Two-table comma list: not supported
15
+ select count(*) as cnt from cj_a, cj_b;
16
+ -- error: multiple FROM sources
17
+
18
+ -- Two-table comma list with WHERE join condition: not supported
19
+ select cj_a.id, cj_b.id from cj_a, cj_b where cj_a.id = cj_b.id - 9;
20
+ -- error: multiple FROM sources
21
+
22
+ -- Three-table comma list: not supported
23
+ select cj_a.id, cj_b.id, cj_c.id from cj_a, cj_b, cj_c
24
+ where cj_a.id = cj_b.id - 9 and cj_b.id = cj_c.id - 90;
25
+ -- error: multiple FROM sources
26
+
27
+ -- Comma mixed with explicit JOIN: still rejected because of the leading comma list
28
+ select cj_a.id, cj_b.id, cj_c.id
29
+ from cj_a, cj_b join cj_c on cj_b.id = cj_c.id - 90
30
+ where cj_a.id = 2;
31
+ -- error: multiple FROM sources
32
+
33
+ drop table cj_a;
34
+ drop table cj_b;
35
+ drop table cj_c;
@@ -0,0 +1,305 @@
1
+ -- 11.3-index-nested-loop-join.sqllogic
2
+ -- Result-equality coverage for the index-nested-loop join (rule-join-physical-selection
3
+ -- + rules/join/index-nested-loop.ts): a small outer joined to a large indexed inner
4
+ -- replaces the inner walk with one IndexSeek per outer row. Every assertion here is
5
+ -- pure row equality, so it holds whichever physical join the optimizer picks — the
6
+ -- plan-shape assertions (that these shapes DO pick the seek) live in
7
+ -- test/optimizer/index-nested-loop.spec.ts.
8
+
9
+ -- Small outer: one row with a NULL join key, one with a key matching nothing.
10
+ CREATE TABLE inl_s (id INTEGER PRIMARY KEY, k INTEGER NULL);
11
+ INSERT INTO inl_s VALUES (1, 5), (2, 7), (3, 500), (4, NULL);
12
+
13
+ -- Large inner: v secondary-indexed; one row with a NULL v (must never match).
14
+ CREATE TABLE inl_big (id INTEGER PRIMARY KEY, v INTEGER NULL, w INTEGER);
15
+ CREATE INDEX inl_idx_v ON inl_big(v);
16
+ INSERT INTO inl_big VALUES
17
+ (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 0), (6, 6, 1), (7, 7, 2), (8, 8, 3),
18
+ (9, 9, 4), (10, 10, 0), (11, 11, 1), (12, 12, 2), (13, 13, 3), (14, 14, 4), (15, 15, 0),
19
+ (16, 16, 1), (17, 17, 2), (18, 18, 3), (19, 19, 4), (20, 20, 0), (21, 21, 1), (22, 22, 2),
20
+ (23, 23, 3), (24, 24, 4), (25, 25, 0), (26, 26, 1), (27, 27, 2), (28, 28, 3), (29, 29, 4),
21
+ (30, 30, 0), (31, 31, 1), (32, 32, 2), (33, 33, 3), (34, 34, 4), (35, 35, 0), (36, 36, 1),
22
+ (37, 37, 2), (38, 38, 3), (39, 39, 4), (40, 40, 0), (41, NULL, 0);
23
+ ANALYZE;
24
+
25
+ -- INNER: only the two matching outer rows survive
26
+ SELECT s.id, b.id AS bid FROM inl_s s JOIN inl_big b ON b.v = s.k ORDER BY s.id;
27
+ → [{"id":1,"bid":5},{"id":2,"bid":7}]
28
+
29
+ -- LEFT: unmatched (k=500) and NULL-key outer rows null-pad — `NULL = v` is UNKNOWN
30
+ SELECT s.id, b.id AS bid FROM inl_s s LEFT JOIN inl_big b ON b.v = s.k ORDER BY s.id;
31
+ → [{"id":1,"bid":5},{"id":2,"bid":7},{"id":3,"bid":null},{"id":4,"bid":null}]
32
+
33
+ -- SEMI (EXISTS): first-match short-circuit; NULL key drops
34
+ SELECT s.id FROM inl_s s WHERE EXISTS (SELECT 1 FROM inl_big b WHERE b.v = s.k) ORDER BY s.id;
35
+ → [{"id":1},{"id":2}]
36
+
37
+ -- ANTI (NOT EXISTS): unmatched AND NULL-key outer rows are kept
38
+ SELECT s.id FROM inl_s s WHERE NOT EXISTS (SELECT 1 FROM inl_big b WHERE b.v = s.k) ORDER BY s.id;
39
+ → [{"id":3},{"id":4}]
40
+
41
+ -- The inner NULL-v row (id 41) matches nothing: NULL keys miss from BOTH sides
42
+ SELECT count(*) AS n FROM inl_s s JOIN inl_big b ON b.v = s.k;
43
+ → [{"n":2}]
44
+
45
+ -- `exists … as` flag joins keep the nested-loop driver, so they CAN take the seek
46
+ -- path — the flag bit must survive and report per-row match truthfully
47
+ SELECT s.id, has_b FROM inl_s s LEFT JOIN inl_big b ON b.v = s.k EXISTS RIGHT AS has_b ORDER BY s.id;
48
+ → [{"id":1,"has_b":true},{"id":2,"has_b":true},{"id":3,"has_b":false},{"id":4,"has_b":false}]
49
+
50
+ -- Self-join: two access sites on the same table must not share a cursor (the
51
+ -- outer walk and the per-row inner seek are distinct scan sites)
52
+ SELECT a.id, b.id AS bid FROM inl_big a JOIN inl_big b ON b.v = a.w WHERE a.id <= 3 ORDER BY a.id;
53
+ → [{"id":1,"bid":1},{"id":2,"bid":2},{"id":3,"bid":3}]
54
+
55
+ -- Three-way left-deep spine where the seek key comes from the INNER-MOST relation:
56
+ -- at the final join the outer row is the composite [t1, t2] row and the t1
57
+ -- attribute must still resolve inside the seek
58
+ CREATE TABLE inl_t1 (id INTEGER PRIMARY KEY, k INTEGER);
59
+ CREATE TABLE inl_t2 (id INTEGER PRIMARY KEY, t1_id INTEGER);
60
+ INSERT INTO inl_t1 VALUES (1, 5), (2, 7);
61
+ INSERT INTO inl_t2 VALUES (10, 1), (20, 2);
62
+ ANALYZE;
63
+ SELECT t1.id, t2.id AS t2id, b.id AS bid
64
+ FROM inl_t1 t1 JOIN inl_t2 t2 ON t2.t1_id = t1.id JOIN inl_big b ON b.v = t1.k
65
+ ORDER BY t1.id;
66
+ → [{"id":1,"t2id":10,"bid":5},{"id":2,"t2id":20,"bid":7}]
67
+
68
+ -- ===== Composite and partial-composite keys =====
69
+
70
+ CREATE TABLE inl_c (a INTEGER, b INTEGER, val TEXT, PRIMARY KEY (a, b));
71
+ INSERT INTO inl_c VALUES
72
+ (1, 1, 'c1'), (2, 2, 'c2'), (3, 3, 'c3'), (4, 4, 'c4'), (0, 5, 'c5'),
73
+ (1, 6, 'c6'), (2, 7, 'c7'), (3, 8, 'c8'), (4, 9, 'c9'), (0, 10, 'c10'),
74
+ (1, 11, 'c11'), (2, 12, 'c12'), (3, 13, 'c13'), (4, 14, 'c14'), (0, 15, 'c15'),
75
+ (1, 16, 'c16'), (2, 17, 'c17'), (3, 18, 'c18'), (4, 19, 'c19'), (0, 20, 'c20'),
76
+ (1, 21, 'c21'), (2, 22, 'c22'), (3, 23, 'c23'), (4, 24, 'c24'), (0, 25, 'c25'),
77
+ (1, 26, 'c26'), (2, 27, 'c27'), (3, 28, 'c28'), (4, 29, 'c29'), (0, 30, 'c30');
78
+ CREATE TABLE inl_s2 (id INTEGER PRIMARY KEY, x INTEGER NULL, y INTEGER NULL, name TEXT);
79
+ INSERT INTO inl_s2 VALUES (1, 3, 3, 'c3'), (2, 2, 7, 'c12'), (3, 4, 999, 'zzz'), (4, NULL, 1, 'c1');
80
+ ANALYZE;
81
+
82
+ -- Both equi pairs covered by the composite PK: one composite seek per outer row
83
+ SELECT s2.id, c.val FROM inl_s2 s2 JOIN inl_c c ON c.a = s2.x AND c.b = s2.y ORDER BY s2.id;
84
+ → [{"id":1,"val":"c3"},{"id":2,"val":"c7"}]
85
+
86
+ -- Partial composite: the index covers only the `a = x` pair; `val = name` stays
87
+ -- enforced by the retained condition (id 2: x=2 candidates are c2,c7,c12,… — only
88
+ -- c12's val matches its name)
89
+ SELECT s2.id, c.b AS cb FROM inl_s2 s2 JOIN inl_c c ON c.a = s2.x AND c.val = s2.name ORDER BY s2.id;
90
+ → [{"id":1,"cb":3},{"id":2,"cb":12}]
91
+
92
+ -- ===== LATERAL with an ON equality (the sibling-reference guard) =====
93
+ -- Before the guard in rule-join-physical-selection, a LATERAL right side reading
94
+ -- the left side's columns with an equality ON was converted to a hash join,
95
+ -- which drains the right side before any left row exists — "No row context
96
+ -- found" at runtime. The guard keeps the nested-loop driver.
97
+
98
+ CREATE TABLE inl_lt (k INTEGER PRIMARY KEY, v INTEGER);
99
+ CREATE TABLE inl_lu (k INTEGER PRIMARY KEY, v INTEGER);
100
+ INSERT INTO inl_lt VALUES (1, 10), (2, 20);
101
+ INSERT INTO inl_lu VALUES (1, 10), (2, 99);
102
+
103
+ SELECT t.k AS tk, x.k AS xk, x.v AS xv
104
+ FROM inl_lt t JOIN LATERAL (SELECT * FROM inl_lu u WHERE u.k = t.k) x ON x.v = t.v;
105
+ → [{"tk":1,"xk":1,"xv":10}]
106
+
107
+ SELECT t.k AS tk, x.k AS xk
108
+ FROM inl_lt t LEFT JOIN LATERAL (SELECT * FROM inl_lu u WHERE u.k = t.k) x ON x.v = t.v
109
+ ORDER BY tk;
110
+ → [{"tk":1,"xk":1},{"tk":2,"xk":null}]
111
+
112
+ -- ===== Collation: both directions of a NOCASE / BINARY join =====
113
+
114
+ -- Inner column declared NOCASE (its index inherits NOCASE); outer defaulted
115
+ -- BINARY. Declared beats defaulted, so the join compares NOCASE and the index
116
+ -- cover matches — the seek path is eligible, and 'n3' must match 'N3'.
117
+ CREATE TABLE inl_cs (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE);
118
+ CREATE INDEX inl_idx_cs ON inl_cs(name);
119
+ INSERT INTO inl_cs VALUES
120
+ (1, 'N1'), (2, 'N2'), (3, 'N3'), (4, 'N4'), (5, 'N5'), (6, 'N6'), (7, 'N7'), (8, 'N8'),
121
+ (9, 'N9'), (10, 'N10'), (11, 'N11'), (12, 'N12'), (13, 'N13'), (14, 'N14'), (15, 'N15'),
122
+ (16, 'N16'), (17, 'N17'), (18, 'N18'), (19, 'N19'), (20, 'N20');
123
+ CREATE TABLE inl_co (id INTEGER PRIMARY KEY, name TEXT);
124
+ INSERT INTO inl_co VALUES (1, 'n3'), (2, 'N5'), (3, 'zzz');
125
+ ANALYZE;
126
+ SELECT o.id, i.id AS iid FROM inl_co o JOIN inl_cs i ON i.name = o.name ORDER BY o.id;
127
+ → [{"id":1,"iid":3},{"id":2,"iid":5}]
128
+
129
+ -- Reverse direction: outer declared NOCASE, inner index BINARY — a raw seek
130
+ -- would under-fetch, so the rule declines and the surviving hash join must
131
+ -- return BOTH case variants of 'A1'.
132
+ CREATE TABLE inl_bs (id INTEGER PRIMARY KEY, name TEXT);
133
+ CREATE INDEX inl_idx_bs ON inl_bs(name);
134
+ INSERT INTO inl_bs VALUES
135
+ (1, 'B1'), (2, 'B2'), (3, 'B3'), (4, 'B4'), (5, 'B5'), (6, 'B6'), (7, 'B7'), (8, 'B8'),
136
+ (9, 'B9'), (10, 'B10'), (11, 'B11'), (12, 'B12'), (13, 'B13'), (14, 'B14'), (15, 'B15'),
137
+ (16, 'B16'), (17, 'B17'), (18, 'B18'), (19, 'B19'), (20, 'B20'), (21, 'a1'), (22, 'A1');
138
+ CREATE TABLE inl_no (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE);
139
+ INSERT INTO inl_no VALUES (1, 'A1');
140
+ ANALYZE;
141
+ SELECT o.id, b.id AS bid FROM inl_no o JOIN inl_bs b ON b.name = o.name ORDER BY bid;
142
+ → [{"id":1,"bid":21},{"id":1,"bid":22}]
143
+
144
+ -- ===== Seek-side election: the index-nested-loop is offered in BOTH orientations =====
145
+ -- The parent/child rollup from ticket feat-index-nested-loop-seek-side-election: a
146
+ -- filtered parent (`txn`, ≈10 rows per entity) and an FK-indexed child (`entry`).
147
+ -- Whichever table is named first, the optimizer drives from the filtered parent and
148
+ -- seeks the child — the second spelling below is rebuilt as a JoinNode with its
149
+ -- children exchanged. Both must return the rows a plain nested loop returns, which
150
+ -- the third query forces (`+ 0` on the key is not a bare column, so no equi pair is
151
+ -- extracted and no physical join algorithm — hash, merge, or seek — competes).
152
+
153
+ CREATE TABLE inl_txn (id INTEGER PRIMARY KEY, entity_id INTEGER, date TEXT);
154
+ CREATE TABLE inl_entry (id INTEGER PRIMARY KEY, txn_id INTEGER, amount REAL);
155
+ CREATE INDEX inl_txn_entity ON inl_txn (entity_id, date);
156
+ CREATE INDEX inl_entry_txn ON inl_entry (txn_id);
157
+ INSERT INTO inl_txn VALUES
158
+ (1, 1, '2024-01-01'), (2, 2, '2024-01-02'), (3, 3, '2024-01-03'), (4, 1, '2024-01-04'),
159
+ (5, 2, '2024-01-05'), (6, 3, '2024-01-06'), (7, 1, '2024-01-07'), (8, 2, '2024-01-08'),
160
+ (9, 3, '2024-01-09'), (10, 1, '2024-01-10'), (11, 2, '2024-01-11'), (12, 3, '2024-01-12'),
161
+ (13, 1, '2024-01-13'), (14, 2, '2024-01-14'), (15, 3, '2024-01-15'), (16, 1, '2024-01-16'),
162
+ (17, 2, '2024-01-17'), (18, 3, '2024-01-18'), (19, 1, '2024-01-19'), (20, 2, '2024-01-20'),
163
+ (21, 3, '2024-01-21'), (22, 1, '2024-01-22'), (23, 2, '2024-01-23'), (24, 3, '2024-01-24');
164
+ INSERT INTO inl_entry VALUES
165
+ (1, 1, 1.5), (2, 2, 3.0), (3, 3, 4.5), (4, 4, 6.0), (5, 5, 7.5), (6, 6, 9.0), (7, 7, 10.5),
166
+ (8, 8, 12.0), (9, 9, 13.5), (10, 10, 15.0), (11, 11, 16.5), (12, 12, 18.0), (13, 13, 19.5),
167
+ (14, 14, 21.0), (15, 15, 22.5), (16, 16, 24.0), (17, 17, 25.5), (18, 18, 27.0), (19, 19, 28.5),
168
+ (20, 20, 30.0), (21, 21, 31.5), (22, 22, 33.0), (23, 23, 34.5), (24, 24, 36.0),
169
+ (25, 1, 0.5), (26, 4, 0.5), (27, 7, 0.5), (28, 10, 0.5), (29, 13, 0.5), (30, 16, 0.5),
170
+ (31, 19, 0.5), (32, 22, 0.5), (33, 2, 100.0), (34, 999, 1.0);
171
+ ANALYZE;
172
+
173
+ -- Filtered parent named first (the un-mirrored orientation)
174
+ SELECT e.txn_id, sum(e.amount) AS total
175
+ FROM inl_txn t JOIN inl_entry e ON t.id = e.txn_id
176
+ WHERE t.entity_id = 1 GROUP BY e.txn_id ORDER BY e.txn_id;
177
+ → [{"txn_id":1,"total":2},{"txn_id":4,"total":6.5},{"txn_id":7,"total":11},{"txn_id":10,"total":15.5},{"txn_id":13,"total":20},{"txn_id":16,"total":24.5},{"txn_id":19,"total":29},{"txn_id":22,"total":33.5}]
178
+
179
+ -- Child named first (the mirrored orientation: same plan shape, sides exchanged)
180
+ SELECT e.txn_id, sum(e.amount) AS total
181
+ FROM inl_entry e JOIN inl_txn t ON t.id = e.txn_id
182
+ WHERE t.entity_id = 1 GROUP BY e.txn_id ORDER BY e.txn_id;
183
+ → [{"txn_id":1,"total":2},{"txn_id":4,"total":6.5},{"txn_id":7,"total":11},{"txn_id":10,"total":15.5},{"txn_id":13,"total":20},{"txn_id":16,"total":24.5},{"txn_id":19,"total":29},{"txn_id":22,"total":33.5}]
184
+
185
+ -- Forced away from every equi-join rewrite: the plain nested-loop answer
186
+ SELECT e.txn_id, sum(e.amount) AS total
187
+ FROM inl_entry e JOIN inl_txn t ON t.id = e.txn_id + 0
188
+ WHERE t.entity_id = 1 GROUP BY e.txn_id ORDER BY e.txn_id;
189
+ → [{"txn_id":1,"total":2},{"txn_id":4,"total":6.5},{"txn_id":7,"total":11},{"txn_id":10,"total":15.5},{"txn_id":13,"total":20},{"txn_id":16,"total":24.5},{"txn_id":19,"total":29},{"txn_id":22,"total":33.5}]
190
+
191
+ -- Non-aggregated projection through the exchanged sides: values follow the
192
+ -- attribute the projection names, not the slot it used to occupy
193
+ SELECT t.id AS tid, t.date, e.id AS eid, e.amount
194
+ FROM inl_entry e JOIN inl_txn t ON t.id = e.txn_id
195
+ WHERE t.entity_id = 1 ORDER BY eid;
196
+ → [{"tid":1,"date":"2024-01-01","eid":1,"amount":1.5},{"tid":4,"date":"2024-01-04","eid":4,"amount":6},{"tid":7,"date":"2024-01-07","eid":7,"amount":10.5},{"tid":10,"date":"2024-01-10","eid":10,"amount":15},{"tid":13,"date":"2024-01-13","eid":13,"amount":19.5},{"tid":16,"date":"2024-01-16","eid":16,"amount":24},{"tid":19,"date":"2024-01-19","eid":19,"amount":28.5},{"tid":22,"date":"2024-01-22","eid":22,"amount":33},{"tid":1,"date":"2024-01-01","eid":25,"amount":0.5},{"tid":4,"date":"2024-01-04","eid":26,"amount":0.5},{"tid":7,"date":"2024-01-07","eid":27,"amount":0.5},{"tid":10,"date":"2024-01-10","eid":28,"amount":0.5},{"tid":13,"date":"2024-01-13","eid":29,"amount":0.5},{"tid":16,"date":"2024-01-16","eid":30,"amount":0.5},{"tid":19,"date":"2024-01-19","eid":31,"amount":0.5},{"tid":22,"date":"2024-01-22","eid":32,"amount":0.5}]
197
+
198
+ -- `SELECT *` is the sharpest test of the swap: with no projection naming columns,
199
+ -- the result's COLUMN ORDER is whatever the star expanded to at build time. It must
200
+ -- stay the spelled order — entry's three columns then txn's — even though the first
201
+ -- query below plans with the sides exchanged and the second does not.
202
+ SELECT * FROM inl_entry e JOIN inl_txn t ON t.id = e.txn_id WHERE t.entity_id = 1 ORDER BY e.id;
203
+ → [{"id":1,"txn_id":1,"amount":1.5,"id:1":1,"entity_id":1,"date":"2024-01-01"},{"id":4,"txn_id":4,"amount":6,"id:1":4,"entity_id":1,"date":"2024-01-04"},{"id":7,"txn_id":7,"amount":10.5,"id:1":7,"entity_id":1,"date":"2024-01-07"},{"id":10,"txn_id":10,"amount":15,"id:1":10,"entity_id":1,"date":"2024-01-10"},{"id":13,"txn_id":13,"amount":19.5,"id:1":13,"entity_id":1,"date":"2024-01-13"},{"id":16,"txn_id":16,"amount":24,"id:1":16,"entity_id":1,"date":"2024-01-16"},{"id":19,"txn_id":19,"amount":28.5,"id:1":19,"entity_id":1,"date":"2024-01-19"},{"id":22,"txn_id":22,"amount":33,"id:1":22,"entity_id":1,"date":"2024-01-22"},{"id":25,"txn_id":1,"amount":0.5,"id:1":1,"entity_id":1,"date":"2024-01-01"},{"id":26,"txn_id":4,"amount":0.5,"id:1":4,"entity_id":1,"date":"2024-01-04"},{"id":27,"txn_id":7,"amount":0.5,"id:1":7,"entity_id":1,"date":"2024-01-07"},{"id":28,"txn_id":10,"amount":0.5,"id:1":10,"entity_id":1,"date":"2024-01-10"},{"id":29,"txn_id":13,"amount":0.5,"id:1":13,"entity_id":1,"date":"2024-01-13"},{"id":30,"txn_id":16,"amount":0.5,"id:1":16,"entity_id":1,"date":"2024-01-16"},{"id":31,"txn_id":19,"amount":0.5,"id:1":19,"entity_id":1,"date":"2024-01-19"},{"id":32,"txn_id":22,"amount":0.5,"id:1":22,"entity_id":1,"date":"2024-01-22"}]
204
+
205
+ SELECT * FROM inl_txn t JOIN inl_entry e ON t.id = e.txn_id WHERE t.entity_id = 1 ORDER BY e.id;
206
+ → [{"id":1,"entity_id":1,"date":"2024-01-01","id:1":1,"txn_id":1,"amount":1.5},{"id":4,"entity_id":1,"date":"2024-01-04","id:1":4,"txn_id":4,"amount":6},{"id":7,"entity_id":1,"date":"2024-01-07","id:1":7,"txn_id":7,"amount":10.5},{"id":10,"entity_id":1,"date":"2024-01-10","id:1":10,"txn_id":10,"amount":15},{"id":13,"entity_id":1,"date":"2024-01-13","id:1":13,"txn_id":13,"amount":19.5},{"id":16,"entity_id":1,"date":"2024-01-16","id:1":16,"txn_id":16,"amount":24},{"id":19,"entity_id":1,"date":"2024-01-19","id:1":19,"txn_id":19,"amount":28.5},{"id":22,"entity_id":1,"date":"2024-01-22","id:1":22,"txn_id":22,"amount":33},{"id":1,"entity_id":1,"date":"2024-01-01","id:1":25,"txn_id":1,"amount":0.5},{"id":4,"entity_id":1,"date":"2024-01-04","id:1":26,"txn_id":4,"amount":0.5},{"id":7,"entity_id":1,"date":"2024-01-07","id:1":27,"txn_id":7,"amount":0.5},{"id":10,"entity_id":1,"date":"2024-01-10","id:1":28,"txn_id":10,"amount":0.5},{"id":13,"entity_id":1,"date":"2024-01-13","id:1":29,"txn_id":13,"amount":0.5},{"id":16,"entity_id":1,"date":"2024-01-16","id:1":30,"txn_id":16,"amount":0.5},{"id":19,"entity_id":1,"date":"2024-01-19","id:1":31,"txn_id":19,"amount":0.5},{"id":22,"entity_id":1,"date":"2024-01-22","id:1":32,"txn_id":22,"amount":0.5}]
207
+
208
+ -- A third relation above the exchanged pair: the upper join consumes the mirrored
209
+ -- join's attributes in their NEW order, so a stale positional mapping there would
210
+ -- surface as swapped values rather than as an error
211
+ CREATE TABLE inl_ent (id INTEGER PRIMARY KEY, name TEXT);
212
+ INSERT INTO inl_ent VALUES (1, 'alpha'), (2, 'beta'), (3, 'gamma');
213
+ ANALYZE;
214
+ SELECT n.name, t.id AS tid, e.id AS eid
215
+ FROM inl_entry e JOIN inl_txn t ON t.id = e.txn_id JOIN inl_ent n ON n.id = t.entity_id
216
+ WHERE t.entity_id = 1 ORDER BY eid;
217
+ → [{"name":"alpha","tid":1,"eid":1},{"name":"alpha","tid":4,"eid":4},{"name":"alpha","tid":7,"eid":7},{"name":"alpha","tid":10,"eid":10},{"name":"alpha","tid":13,"eid":13},{"name":"alpha","tid":16,"eid":16},{"name":"alpha","tid":19,"eid":19},{"name":"alpha","tid":22,"eid":22},{"name":"alpha","tid":1,"eid":25},{"name":"alpha","tid":4,"eid":26},{"name":"alpha","tid":7,"eid":27},{"name":"alpha","tid":10,"eid":28},{"name":"alpha","tid":13,"eid":29},{"name":"alpha","tid":16,"eid":30},{"name":"alpha","tid":19,"eid":31},{"name":"alpha","tid":22,"eid":32}]
218
+
219
+ -- Self-join that mirrors: `b` (filtered to 8 rows) drives and `a` is seeked by
220
+ -- primary key, the reverse of how it is spelled. The two sides are distinct scan
221
+ -- instances of one table, so the swap must not make them share a cursor.
222
+ SELECT a.id AS aid, b.id AS bid FROM inl_big a JOIN inl_big b ON a.id = b.v WHERE b.w = 3 ORDER BY aid;
223
+ → [{"aid":3,"bid":3},{"aid":8,"bid":8},{"aid":13,"bid":13},{"aid":18,"bid":18},{"aid":23,"bid":23},{"aid":28,"bid":28},{"aid":33,"bid":33},{"aid":38,"bid":38}]
224
+
225
+ -- Both sides indexed on the join key, large table named first: the swap drives from
226
+ -- the small side and seeks the large one; rows are what an inner join returns
227
+ CREATE TABLE inl_pa (id INTEGER PRIMARY KEY, k INTEGER NULL);
228
+ CREATE INDEX inl_idx_pa_k ON inl_pa(k);
229
+ INSERT INTO inl_pa VALUES (1, 3), (2, 7), (3, 40), (4, NULL);
230
+ ANALYZE;
231
+ SELECT b.id AS bid, a.id AS aid FROM inl_big b JOIN inl_pa a ON b.v = a.k ORDER BY aid;
232
+ → [{"bid":3,"aid":1},{"bid":7,"aid":2},{"bid":40,"aid":3}]
233
+
234
+ -- ===== Join-key seek combined with a predicate the module already claimed =====
235
+ -- `status` is indexed, so `b.status = 'x'` is pushed into the inner leaf as a seek
236
+ -- the module enforces alone — it is nowhere else in the tree. The index-nested-loop
237
+ -- rule re-offers that predicate together with the join key and re-applies whatever
238
+ -- the module declines as a Filter directly above the new seek (or the module takes
239
+ -- both on a composite index). Every outer key below matches an inner row by id, so
240
+ -- the status predicate is what decides each row: k=5 hits id 5 (status 'y') and
241
+ -- must be dropped — a redundant filter would prove nothing.
242
+ CREATE TABLE inl_ps (id INTEGER PRIMARY KEY, k INTEGER NULL);
243
+ INSERT INTO inl_ps VALUES (1, 5), (2, 8), (3, 12), (4, NULL), (5, 500);
244
+ CREATE TABLE inl_pb (id INTEGER PRIMARY KEY, v INTEGER, status TEXT);
245
+ CREATE INDEX inl_idx_pb_v ON inl_pb(v);
246
+ CREATE INDEX inl_idx_pb_status ON inl_pb(status);
247
+ INSERT INTO inl_pb VALUES
248
+ (1, 1, 'y'), (2, 2, 'y'), (3, 3, 'y'), (4, 4, 'x'), (5, 5, 'y'), (6, 6, 'y'), (7, 7, 'y'), (8, 8, 'x'),
249
+ (9, 9, 'y'), (10, 10, 'y'), (11, 11, 'y'), (12, 12, 'x'), (13, 13, 'y'), (14, 14, 'y'), (15, 15, 'y'),
250
+ (16, 16, 'x'), (17, 17, 'y'), (18, 18, 'y'), (19, 19, 'y'), (20, 20, 'x'), (21, 21, 'y'), (22, 22, 'y'),
251
+ (23, 23, 'y'), (24, 24, 'x'), (25, 25, 'y'), (26, 26, 'y'), (27, 27, 'y'), (28, 28, 'x'), (29, 29, 'y'),
252
+ (30, 30, 'y'), (31, 31, 'y'), (32, 32, 'x'), (33, 33, 'y'), (34, 34, 'y'), (35, 35, 'y'), (36, 36, 'x'),
253
+ (37, 37, 'y'), (38, 38, 'y'), (39, 39, 'y'), (40, 40, 'x');
254
+ ANALYZE;
255
+
256
+ -- INNER on the primary key: id 5 is status 'y' → dropped by the re-applied predicate
257
+ SELECT s.id, b.id AS bid FROM inl_ps s JOIN inl_pb b ON b.id = s.k WHERE b.status = 'x' ORDER BY s.id;
258
+ → [{"id":2,"bid":8},{"id":3,"bid":12}]
259
+
260
+ -- INNER on the secondary index: same answer through idx_v
261
+ SELECT s.id, b.id AS bid FROM inl_ps s JOIN inl_pb b ON b.v = s.k WHERE b.status = 'x' ORDER BY s.id;
262
+ → [{"id":2,"bid":8},{"id":3,"bid":12}]
263
+
264
+ -- LEFT with the predicate in the ON clause: the inner Filter sits inside the inner
265
+ -- pipeline, so a row it rejects makes the seek look empty → null-pad (k=5), as do
266
+ -- the NULL key and the unmatched key
267
+ SELECT s.id, b.id AS bid FROM inl_ps s LEFT JOIN inl_pb b ON b.id = s.k AND b.status = 'x' ORDER BY s.id;
268
+ → [{"id":1,"bid":null},{"id":2,"bid":8},{"id":3,"bid":12},{"id":4,"bid":null},{"id":5,"bid":null}]
269
+
270
+ -- ANTI (NOT EXISTS): a seek hit rejected by the Filter counts as "no inner row" → kept
271
+ SELECT s.id FROM inl_ps s WHERE NOT EXISTS (SELECT 1 FROM inl_pb b WHERE b.id = s.k AND b.status = 'x') ORDER BY s.id;
272
+ → [{"id":1},{"id":4},{"id":5}]
273
+
274
+ -- SEMI (EXISTS): the mirror of the anti answer
275
+ SELECT s.id FROM inl_ps s WHERE EXISTS (SELECT 1 FROM inl_pb b WHERE b.id = s.k AND b.status = 'x') ORDER BY s.id;
276
+ → [{"id":2},{"id":3}]
277
+
278
+ -- A pushed range on the join-key column itself: `b.id = s.k` wins the column as the
279
+ -- seek and `b.id > 10` comes back as the residual (id 5 and 8 are out)
280
+ SELECT s.id, b.id AS bid FROM inl_ps s JOIN inl_pb b ON b.id = s.k WHERE b.id > 10 ORDER BY s.id;
281
+ → [{"id":3,"bid":12}]
282
+
283
+ -- A pushed BETWEEN re-applied as its single node: id 12 is outside the range
284
+ SELECT s.id, b.id AS bid FROM inl_ps s JOIN inl_pb b ON b.v = s.k WHERE b.id BETWEEN 2 AND 9 ORDER BY s.id;
285
+ → [{"id":1,"bid":5},{"id":2,"bid":8}]
286
+
287
+ -- Composite index covering both: the module takes the status equality AND the join
288
+ -- key as one seek, so no residual is needed — rows identical to the single-index form
289
+ CREATE TABLE inl_cb (id INTEGER PRIMARY KEY, v INTEGER, status TEXT);
290
+ CREATE INDEX inl_idx_cb ON inl_cb(status, v);
291
+ CREATE INDEX inl_idx_cb1 ON inl_cb(status);
292
+ INSERT INTO inl_cb SELECT id, v, status FROM inl_pb;
293
+ ANALYZE;
294
+ SELECT s.id, c.id AS cid FROM inl_ps s JOIN inl_cb c ON c.v = s.k WHERE c.status = 'x' ORDER BY s.id;
295
+ → [{"id":2,"cid":8},{"id":3,"cid":12}]
296
+
297
+ -- The module keeps its own seek (a unique primary-key equality) and refuses the join
298
+ -- key: the plan is left alone and the rows are still right
299
+ SELECT s.id, b.id AS bid FROM inl_ps s JOIN inl_pb b ON b.v = s.k WHERE b.id = 8 ORDER BY s.id;
300
+ → [{"id":2,"bid":8}]
301
+
302
+ -- The predicate's column is not selected by the derived table: the re-applied Filter
303
+ -- goes below the peeled Project, where `status` is still in scope
304
+ SELECT s.id FROM inl_ps s JOIN (SELECT id, v FROM inl_pb WHERE status = 'x') t ON t.v = s.k ORDER BY s.id;
305
+ → [{"id":2},{"id":3}]