@quereus/quereus 4.20.1 → 4.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +7 -4
- package/test/README.md +173 -0
- package/test/logic/01-basic.sqllogic +39 -0
- package/test/logic/01.1-select-projection-extras.sqllogic +130 -0
- package/test/logic/01.2-strings.sqllogic +43 -0
- package/test/logic/01.5-insert-select.sqllogic +237 -0
- package/test/logic/01.6-update-extras.sqllogic +118 -0
- package/test/logic/01.7-update-from.sqllogic +67 -0
- package/test/logic/01.8-delete-extras.sqllogic +122 -0
- package/test/logic/01.8.1-delete-range-cow.sqllogic +115 -0
- package/test/logic/01.9-query-expr-dml-gates.sqllogic +32 -0
- package/test/logic/01.9-query-expr-dml.sqllogic +171 -0
- package/test/logic/01.9-query-expr-values.sqllogic +111 -0
- package/test/logic/01.9.1-limit-over-dml-subquery.sqllogic +119 -0
- package/test/logic/02-filters.sqllogic +54 -0
- package/test/logic/02-smoke.sqllogic +62 -0
- package/test/logic/02.1-bind-parameters.sqllogic +223 -0
- package/test/logic/03-expressions.sqllogic +190 -0
- package/test/logic/03.1-quoted-identifiers.sqllogic +55 -0
- package/test/logic/03.4-defaults.sqllogic +554 -0
- package/test/logic/03.4.1-default-edge-cases.sqllogic +99 -0
- package/test/logic/03.5-tvf.sqllogic +61 -0
- package/test/logic/03.6-type-system.sqllogic +544 -0
- package/test/logic/03.6.1-numeric-text-comparison-coercion.sqllogic +244 -0
- package/test/logic/03.6.2-value-to-text.sqllogic +224 -0
- package/test/logic/03.7-bigint-mixed-arithmetic.sqllogic +65 -0
- package/test/logic/03.8-not-precedence.sqllogic +255 -0
- package/test/logic/03.9-is-bool-predicate.sqllogic +150 -0
- package/test/logic/03.9.1-is-bool-predicate-partial-index.sqllogic +34 -0
- package/test/logic/04-order-by.sqllogic +38 -0
- package/test/logic/04-transactions.sqllogic +239 -0
- package/test/logic/04a-savepoint-lazy-attach.sqllogic +122 -0
- package/test/logic/05-vtab_memory.sqllogic +50 -0
- package/test/logic/05.0.1-vtab-memory-unique-index-collation.sqllogic +34 -0
- package/test/logic/05.1-composite-pk-range-scan.sqllogic +147 -0
- package/test/logic/05.1.1-secondary-index-range-scan.sqllogic +56 -0
- package/test/logic/05.2-cast-seek-correctness.sqllogic +124 -0
- package/test/logic/06-builtin_functions.sqllogic +149 -0
- package/test/logic/06.1-string-functions.sqllogic +92 -0
- package/test/logic/06.1.3-like-glob-edges.sqllogic +53 -0
- package/test/logic/06.2-math-functions.sqllogic +82 -0
- package/test/logic/06.3-schema.sqllogic +48 -0
- package/test/logic/06.3.1-schema-all-schemas.sqllogic +39 -0
- package/test/logic/06.3.2-schema-foreign-keys.sqllogic +208 -0
- package/test/logic/06.3.3-introspection-tags.sqllogic +202 -0
- package/test/logic/06.3.3.1-introspection-tags-index.sqllogic +74 -0
- package/test/logic/06.3.4-view-info.sqllogic +378 -0
- package/test/logic/06.3.5-column-info.sqllogic +347 -0
- package/test/logic/06.3.6-schema-views.sqllogic +29 -0
- package/test/logic/06.4-schema-search-path.sqllogic +354 -0
- package/test/logic/06.4.1-schema-case-insensitive.sqllogic +83 -0
- package/test/logic/06.4.2-collation-extras.sqllogic +472 -0
- package/test/logic/06.4.2.1-collation-index-access-paths.sqllogic +140 -0
- package/test/logic/06.4.3-write-path-collation.sqllogic +99 -0
- package/test/logic/06.4.4-comparison-collation-precedence.sqllogic +342 -0
- package/test/logic/06.4.5-any-collate-declared-keys.sqllogic +92 -0
- package/test/logic/06.5-polymorphic-types.sqllogic +169 -0
- package/test/logic/06.5.1-conversion-functions.sqllogic +173 -0
- package/test/logic/06.5.2-scalar-minmax.sqllogic +16 -0
- package/test/logic/06.5.3-undeclared-return-type-comparison.sqllogic +244 -0
- package/test/logic/06.5.4-declared-return-type-builtins.sqllogic +288 -0
- package/test/logic/06.6-aggregate-extended.sqllogic +78 -0
- package/test/logic/06.7-json-extended.sqllogic +53 -0
- package/test/logic/06.8-json-path-operators.sqllogic +99 -0
- package/test/logic/06.9-json-canonical-key.sqllogic +65 -0
- package/test/logic/06.9.1-json-coerce-once.sqllogic +417 -0
- package/test/logic/06.9.1.1-json-coerce-once-index.sqllogic +39 -0
- package/test/logic/06.9.2-json-structural-equality.sqllogic +382 -0
- package/test/logic/06.9.2.1-json-structural-equality-index.sqllogic +76 -0
- package/test/logic/06.9.3-json-index-range-seek.sqllogic +205 -0
- package/test/logic/07-aggregates.sqllogic +193 -0
- package/test/logic/07.1-aggregate-filter-clause.sqllogic +12 -0
- package/test/logic/07.2-aggregate-order-by.sqllogic +11 -0
- package/test/logic/07.3-group-by-extras.sqllogic +602 -0
- package/test/logic/07.3.1-group-by-order-by-key.sqllogic +164 -0
- package/test/logic/07.3.2-grouped-select-list-shape.sqllogic +231 -0
- package/test/logic/07.4-group-by-filter-composite-pk.sqllogic +120 -0
- package/test/logic/07.5-sum-mixed-exact-and-float.sqllogic +151 -0
- package/test/logic/07.5-window.sqllogic +1941 -0
- package/test/logic/07.5.1-window-named.sqllogic +13 -0
- package/test/logic/07.5.2-window-nth-value.sqllogic +13 -0
- package/test/logic/07.6-subqueries.sqllogic +78 -0
- package/test/logic/07.6.1-subquery-extras.sqllogic +93 -0
- package/test/logic/07.7-and-or-short-circuit.sqllogic +58 -0
- package/test/logic/07.7-in-subquery-caching.sqllogic +174 -0
- package/test/logic/07.7-scalar-agg-decorrelation.sqllogic +197 -0
- package/test/logic/07.7.1-in-extras.sqllogic +158 -0
- package/test/logic/07.7.1-scalar-agg-decorrelation-filter.sqllogic +105 -0
- package/test/logic/07.7.2-scalar-agg-decorrelation-nested.sqllogic +115 -0
- package/test/logic/07.7.3-exists-in-select-decorrelation.sqllogic +107 -0
- package/test/logic/07.7.4-where-conjunct-ordering.sqllogic +121 -0
- package/test/logic/07.7.5-filter-lost-under-index-order.sqllogic +134 -0
- package/test/logic/07.7.6-correlated-predicate-scope.sqllogic +178 -0
- package/test/logic/07.7.7-join-source-scope-shadowing.sqllogic +137 -0
- package/test/logic/07.7.8-correlated-ref-to-computed-column.sqllogic +165 -0
- package/test/logic/07.7.9-conjunct-monotonicity.sqllogic +200 -0
- package/test/logic/07.8-correlated-subquery-edges.sqllogic +71 -0
- package/test/logic/07.8.1-correlated-in-decorrelation-gates.sqllogic +156 -0
- package/test/logic/07.9-in-value-list.sqllogic +154 -0
- package/test/logic/07.9.1-in-value-list-composite-index.sqllogic +38 -0
- package/test/logic/08-views.sqllogic +64 -0
- package/test/logic/08.1-semi-anti-join.sqllogic +191 -0
- package/test/logic/08.1-view-edge-cases.sqllogic +118 -0
- package/test/logic/08.1.1-uncorrelated-in-semijoin.sqllogic +251 -0
- package/test/logic/08.2-existence-flag-semijoin-recovery.sqllogic +197 -0
- package/test/logic/08.3-existence-flag-inner-recovery.sqllogic +102 -0
- package/test/logic/08.4-key-set-semi-join.sqllogic +447 -0
- package/test/logic/09-set_operations.sqllogic +244 -0
- package/test/logic/09.1-set-op-cross-collation.sqllogic +230 -0
- package/test/logic/10-distinct_datatypes.sqllogic +127 -0
- package/test/logic/10.1-ddl-lifecycle.sqllogic +203 -0
- package/test/logic/10.1.1-create-table-syntax-edges.sqllogic +47 -0
- package/test/logic/10.1.2-ddl-in-transaction.sqllogic +143 -0
- package/test/logic/10.1.2.1-add-constraint-in-transaction.sqllogic +59 -0
- package/test/logic/10.1.3-ddl-drop-in-transaction.sqllogic +187 -0
- package/test/logic/10.1.3.1-drop-constraint-in-transaction.sqllogic +45 -0
- package/test/logic/10.1.4-create-table-duplicate-constraint-names.sqllogic +149 -0
- package/test/logic/10.1.4-ddl-transaction-policy.sqllogic +314 -0
- package/test/logic/10.1.5-alter-add-constraint-minted-name-disambiguation.sqllogic +219 -0
- package/test/logic/10.1.5-create-index-lifecycle.sqllogic +72 -0
- package/test/logic/10.1.6-ddl-transaction-policy-index.sqllogic +143 -0
- package/test/logic/10.1.7-create-table-default-type-check.sqllogic +119 -0
- package/test/logic/10.1.8-alter-add-check-existing-rows.sqllogic +117 -0
- package/test/logic/10.2-column-features.sqllogic +365 -0
- package/test/logic/10.2.1-table-options-rejection.sqllogic +63 -0
- package/test/logic/10.2.2-default-collation-memory.sqllogic +30 -0
- package/test/logic/10.3-function-features.sqllogic +247 -0
- package/test/logic/10.4-schema-scale.sqllogic +168 -0
- package/test/logic/10.4.1-schema-scale-indexes.sqllogic +121 -0
- package/test/logic/10.5-indexes.sqllogic +164 -0
- package/test/logic/10.5.1-partial-indexes.sqllogic +526 -0
- package/test/logic/10.5.2-expression-indexes.sqllogic +34 -0
- package/test/logic/10.5.3-desc-index-ordering.sqllogic +130 -0
- package/test/logic/10.5.4-composite-pk-index-update-phantom.sqllogic +55 -0
- package/test/logic/10.5.5-index-name-uniqueness.sqllogic +76 -0
- package/test/logic/10.5.6-minmax-index-boundary.sqllogic +204 -0
- package/test/logic/10.5.7-implicit-unique-index-lifecycle.sqllogic +1216 -0
- package/test/logic/10.6-distinct-edge-cases.sqllogic +119 -0
- package/test/logic/100-predicate-normalization-edge-cases.sqllogic +238 -0
- package/test/logic/100.1-where-extras.sqllogic +163 -0
- package/test/logic/100.1.1-where-join-on-order-indexed.sqllogic +35 -0
- package/test/logic/101-builtin-mutation-kills.sqllogic +974 -0
- package/test/logic/101-transaction-edge-cases.sqllogic +395 -0
- package/test/logic/102-schema-catalog-edge-cases.sqllogic +252 -0
- package/test/logic/102-unique-constraints.sqllogic +145 -0
- package/test/logic/102.0.1-schema-catalog-indexed-table.sqllogic +32 -0
- package/test/logic/102.1-unique-edge-cases.sqllogic +111 -0
- package/test/logic/102.1.1-unique-index-creation-edge-cases.sqllogic +75 -0
- package/test/logic/102.2-unique-collation.sqllogic +181 -0
- package/test/logic/102.2.1-unique-collation-index-derived.sqllogic +305 -0
- package/test/logic/103-database-options-edge-cases.sqllogic +108 -0
- package/test/logic/103.1-pragma-boolean-values.sqllogic +126 -0
- package/test/logic/104-emit-mutation-kills.sqllogic +236 -0
- package/test/logic/105-vtab-memory-mutation-kills.sqllogic +754 -0
- package/test/logic/105.1-vtab-memory-index-mutation-kills.sqllogic +540 -0
- package/test/logic/106-constraint-extractor-mutation-kills.sqllogic +350 -0
- package/test/logic/107-temporal-arithmetic-mutation-kills.sqllogic +833 -0
- package/test/logic/108-cardinality-estimation.sqllogic +146 -0
- package/test/logic/108.1-analyze-name-resolution.sqllogic +102 -0
- package/test/logic/109-aggregate-physical-selection.sqllogic +103 -0
- package/test/logic/11-joins.sqllogic +86 -0
- package/test/logic/11.1-join-using.sqllogic +267 -0
- package/test/logic/11.2-comma-join.sqllogic +35 -0
- package/test/logic/11.3-index-nested-loop-join.sqllogic +305 -0
- package/test/logic/11.3-mismatched-collation-join.sqllogic +74 -0
- package/test/logic/11.4-hash-join-side-swap.sqllogic +118 -0
- package/test/logic/110-scan-emitter-mutation-kills.sqllogic +121 -0
- package/test/logic/110.1-scan-emitter-index-scan-kills.sqllogic +47 -0
- package/test/logic/12-empty-primary-key.sqllogic +124 -0
- package/test/logic/12-join_padding_order.sqllogic +58 -0
- package/test/logic/12.1-orderby-index-consumption.sqllogic +40 -0
- package/test/logic/13-cte.sqllogic +137 -0
- package/test/logic/13.1-cte-multiple-recursive.sqllogic +104 -0
- package/test/logic/13.10-cte-qualified-name.sqllogic +136 -0
- package/test/logic/13.11-unreferenced-dml-cte.sqllogic +414 -0
- package/test/logic/13.12-nested-dml-cte-rejected.sqllogic +280 -0
- package/test/logic/13.2-cte-bind-params.sqllogic +98 -0
- package/test/logic/13.3-cte-edge-cases.sqllogic +120 -0
- package/test/logic/13.4-cte-extras.sqllogic +98 -0
- package/test/logic/13.5-cte-join-order.sqllogic +166 -0
- package/test/logic/13.6-cte-dml-runs-once.sqllogic +318 -0
- package/test/logic/13.7-cte-sibling-visibility.sqllogic +227 -0
- package/test/logic/13.8-insert-with-clause-visibility.sqllogic +254 -0
- package/test/logic/13.9-schema-authored-cte-isolation.sqllogic +328 -0
- package/test/logic/13.9.1-schema-authored-schema-path-isolation.sqllogic +408 -0
- package/test/logic/14-utilities.sqllogic +168 -0
- package/test/logic/15-timespan.sqllogic +279 -0
- package/test/logic/15.1-semantic-ordering.sqllogic +960 -0
- package/test/logic/15.1.1-json-check-coercion.sqllogic +178 -0
- package/test/logic/15.1.2-semantic-ordering-index.sqllogic +41 -0
- package/test/logic/15.1.3-declared-form-write-contract.sqllogic +519 -0
- package/test/logic/16-epoch.sqllogic +187 -0
- package/test/logic/17-weekday-modifier.sqllogic +78 -0
- package/test/logic/18-json-string-escapes.sqllogic +86 -0
- package/test/logic/18-strftime-extended-formats.sqllogic +68 -0
- package/test/logic/20-empty-single-row.sqllogic +261 -0
- package/test/logic/21-null-edge-cases.sqllogic +227 -0
- package/test/logic/21.1-where-null-comparisons.sqllogic +151 -0
- package/test/logic/21.1.1-where-null-comparisons-secondary-index.sqllogic +35 -0
- package/test/logic/21.2-between-edges.sqllogic +157 -0
- package/test/logic/22-boundary-values.sqllogic +175 -0
- package/test/logic/23-self-joins-duplicates.sqllogic +74 -0
- package/test/logic/24-builtin-branches.sqllogic +611 -0
- package/test/logic/24.1-substr-extras.sqllogic +75 -0
- package/test/logic/24.2-random-extras.sqllogic +39 -0
- package/test/logic/25-aggregate-edge-cases.sqllogic +289 -0
- package/test/logic/25.1-nested-aggregates.sqllogic +74 -0
- package/test/logic/25.2-having-edge-cases.sqllogic +318 -0
- package/test/logic/25.3-aggregate-isnull-empty.sqllogic +16 -0
- package/test/logic/25.4-groupby-fd-constant-empty.sqllogic +55 -0
- package/test/logic/25.5-ungrouped-aggregate-column-free-select-item.sqllogic +196 -0
- package/test/logic/26-join-edge-cases.sqllogic +333 -0
- package/test/logic/26.1-left-join-isnull-on-notnull.sqllogic +26 -0
- package/test/logic/26.2-left-join-on-vs-where.sqllogic +67 -0
- package/test/logic/26.3-join-on-constants.sqllogic +61 -0
- package/test/logic/26.4-join-on-is-null-nullable.sqllogic +44 -0
- package/test/logic/27-window-edge-cases.sqllogic +206 -0
- package/test/logic/27.1-window-groups-frame.sqllogic +13 -0
- package/test/logic/27.2-window-exclude.sqllogic +13 -0
- package/test/logic/27.3-window-json-aggregation.sqllogic +66 -0
- package/test/logic/28-set-ops-sort-edge-cases.sqllogic +444 -0
- package/test/logic/28.1-compound-limit-offset.sqllogic +143 -0
- package/test/logic/28.2-orderby-expression-extras.sqllogic +698 -0
- package/test/logic/28.2-set-op-branch-types.sqllogic +217 -0
- package/test/logic/29-constraint-edge-cases.sqllogic +442 -0
- package/test/logic/29.1-column-level-conflict-clause.sqllogic +171 -0
- package/test/logic/29.2-table-level-pk-conflict-clause.sqllogic +113 -0
- package/test/logic/40-constraints.sqllogic +418 -0
- package/test/logic/40.1-pk-desc-direction.sqllogic +76 -0
- package/test/logic/40.2-check-extras.sqllogic +683 -0
- package/test/logic/41-alter-table.sqllogic +191 -0
- package/test/logic/41-fk-cascade-conflict-and-self-ref.sqllogic +263 -0
- package/test/logic/41-fk-cross-schema.sqllogic +155 -0
- package/test/logic/41-fk-extended-targets.sqllogic +173 -0
- package/test/logic/41-foreign-keys.sqllogic +1001 -0
- package/test/logic/41-generated-column-errors.sqllogic +225 -0
- package/test/logic/41-generated-column-extras.sqllogic +598 -0
- package/test/logic/41-generated-column-scope.sqllogic +329 -0
- package/test/logic/41-generated-columns.sqllogic +361 -0
- package/test/logic/41.1-alter-pk.sqllogic +230 -0
- package/test/logic/41.1-fk-collation-conflict.sqllogic +277 -0
- package/test/logic/41.10-alter-drop-column-foreign-key.sqllogic +274 -0
- package/test/logic/41.10.1-alter-drop-column-unique-index.sqllogic +169 -0
- package/test/logic/41.10.2-alter-drop-column-check-and-assertion.sqllogic +1616 -0
- package/test/logic/41.10.3-alter-drop-column-referencing-fk.sqllogic +292 -0
- package/test/logic/41.10.4-drop-object-expression-dependents.sqllogic +602 -0
- package/test/logic/41.11-deferred-fk-with-rename.sqllogic +232 -0
- package/test/logic/41.12-alter-default-coercion.sqllogic +200 -0
- package/test/logic/41.13-alter-add-column-generated-backfill.sqllogic +566 -0
- package/test/logic/41.14-alter-add-column-subquery-backfill.sqllogic +129 -0
- package/test/logic/41.15-alter-column-default-type-check.sqllogic +178 -0
- package/test/logic/41.16-fk-unenforceable.sqllogic +364 -0
- package/test/logic/41.2-alter-column.sqllogic +382 -0
- package/test/logic/41.2.1-alter-column-retype-deleted-row-memory.sqllogic +32 -0
- package/test/logic/41.2.2-alter-column-retype-index-rebuild.sqllogic +77 -0
- package/test/logic/41.2.3-alter-column-set-not-null-pk-backfill.sqllogic +310 -0
- package/test/logic/41.2.3.1-alter-column-set-not-null-pk-backfill-secondary-index.sqllogic +37 -0
- package/test/logic/41.3-alter-add-column-unique.sqllogic +184 -0
- package/test/logic/41.3-alter-rename-propagation.sqllogic +2352 -0
- package/test/logic/41.3.1-alter-rename-index-propagation.sqllogic +94 -0
- package/test/logic/41.4-alter-add-column-constraints.sqllogic +855 -0
- package/test/logic/41.5-alter-misc.sqllogic +48 -0
- package/test/logic/41.5-cross-schema-foreign-keys.sqllogic +253 -0
- package/test/logic/41.6-alter-drop-rename-constraint.sqllogic +354 -0
- package/test/logic/41.6.1-alter-drop-constraint-index-derived.sqllogic +22 -0
- package/test/logic/41.7-alter-column-collate.sqllogic +105 -0
- package/test/logic/41.7.1-alter-column-collate-unique.sqllogic +45 -0
- package/test/logic/41.7.2-alter-column-collate-unique-store.sqllogic +208 -0
- package/test/logic/41.7.2.1-alter-column-collate-unique-index-store.sqllogic +114 -0
- package/test/logic/41.7.3-alter-column-retype-unique.sqllogic +293 -0
- package/test/logic/41.7.3.1-alter-column-retype-staged-rows-memory.sqllogic +41 -0
- package/test/logic/41.7.3.2-alter-column-retype-unique-index.sqllogic +72 -0
- package/test/logic/41.7.4-alter-column-retype-semantic-memory.sqllogic +445 -0
- package/test/logic/41.7.4.1-alter-column-retype-semantic-index-memory.sqllogic +91 -0
- package/test/logic/41.7.5-alter-column-collate-pk-staged-delete-memory.sqllogic +106 -0
- package/test/logic/41.8-alter-add-constraint-unique-fk.sqllogic +275 -0
- package/test/logic/41.8-alter-savepoint-staged-rows.sqllogic +443 -0
- package/test/logic/41.8.1-alter-rename-savepoint-staged-rows.sqllogic +408 -0
- package/test/logic/41.9-alter-constraint-body-change.sqllogic +180 -0
- package/test/logic/41.9-fk-restrict-batched.sqllogic +290 -0
- package/test/logic/42-committed-snapshot.sqllogic +254 -0
- package/test/logic/42-returning.sqllogic +159 -0
- package/test/logic/42.1-returning-extras.sqllogic +232 -0
- package/test/logic/42.2-returning-stored-row.sqllogic +165 -0
- package/test/logic/43-default-nullability.sqllogic +131 -0
- package/test/logic/43-transition-constraints.sqllogic +341 -0
- package/test/logic/43.1-default-collation.sqllogic +140 -0
- package/test/logic/43.1-notnull-or-conflict.sqllogic +79 -0
- package/test/logic/43.2-deferred-check-new-on-delete.sqllogic +95 -0
- package/test/logic/43.3-nullable-primary-key.sqllogic +587 -0
- package/test/logic/44-determinism-validation.sqllogic +164 -0
- package/test/logic/44-orthogonality.sqllogic +146 -0
- package/test/logic/44.1-nondeterministic-schema.sqllogic +316 -0
- package/test/logic/45-udf-determinism.sqllogic +39 -0
- package/test/logic/46-mutation-context.sqllogic +595 -0
- package/test/logic/47-upsert.sqllogic +255 -0
- package/test/logic/47.1-upsert-conflict-targets.sqllogic +169 -0
- package/test/logic/47.2-replace-and-or-clauses.sqllogic +164 -0
- package/test/logic/47.2.1-pk-replace-secondary-unique.sqllogic +374 -0
- package/test/logic/47.3-engine-or-fail.sqllogic +122 -0
- package/test/logic/47.3-upsert-conflict-target-collation.sqllogic +139 -0
- package/test/logic/47.3.1-upsert-conflict-index-derived-collation.sqllogic +25 -0
- package/test/logic/47.4-upsert-conflict-target-affinity.sqllogic +59 -0
- package/test/logic/47.5-upsert-do-update-constraints.sqllogic +440 -0
- package/test/logic/47.6-upsert-do-update-decomposed.sqllogic +100 -0
- package/test/logic/49-reference-graph.sqllogic +84 -0
- package/test/logic/50-declarative-schema.sqllogic +2550 -0
- package/test/logic/50-metadata-tags.sqllogic +1033 -0
- package/test/logic/50.0.1-metadata-tags-index.sqllogic +141 -0
- package/test/logic/50.1-declare-schema-pk.sqllogic +150 -0
- package/test/logic/50.2-declare-schema-renames.sqllogic +949 -0
- package/test/logic/50.2.1-declare-schema-index-renames.sqllogic +173 -0
- package/test/logic/50.3-declare-schema-duplicate-names.sqllogic +280 -0
- package/test/logic/50.4-declare-schema-apply-restore.sqllogic +192 -0
- package/test/logic/51-lens-foundation.sqllogic +147 -0
- package/test/logic/51-materialized-views.sqllogic +376 -0
- package/test/logic/51.5-materialized-views-coarsened-key.sqllogic +198 -0
- package/test/logic/51.7-maintained-table-attach-detach.sqllogic +468 -0
- package/test/logic/51.8-maintained-table-declared-constraints.sqllogic +440 -0
- package/test/logic/51.85-maintained-table-parent-fk.sqllogic +103 -0
- package/test/logic/51.9-maintained-table-secondary-unique.sqllogic +237 -0
- package/test/logic/51.9.1-maintained-table-partial-unique.sqllogic +36 -0
- package/test/logic/52-lens-overrides.sqllogic +566 -0
- package/test/logic/53-materialized-views-rowtime.sqllogic +1601 -0
- package/test/logic/53-reserved-tags.sqllogic +252 -0
- package/test/logic/53.1-materialized-view-write-through.sqllogic +329 -0
- package/test/logic/53.2-materialized-view-rename-propagation.sqllogic +483 -0
- package/test/logic/53.3-materialized-view-constraint-only-ddl.sqllogic +409 -0
- package/test/logic/53.3.1-materialized-view-index-ddl-irrelevance.sqllogic +29 -0
- package/test/logic/53.4-materialized-view-structural-alter-restore.sqllogic +436 -0
- package/test/logic/54-covering-mv-enforcement.sqllogic +314 -0
- package/test/logic/54.1-covering-mv-partial-enforcement.sqllogic +34 -0
- package/test/logic/55-internal-eviction-reporting.sqllogic +89 -0
- package/test/logic/55-lens-prover.sqllogic +202 -0
- package/test/logic/55.1-eviction-restrict-fk.sqllogic +148 -0
- package/test/logic/55.5-lens-authored-inverse.sqllogic +1468 -0
- package/test/logic/80-grow-retrieve-pass.sqllogic +79 -0
- package/test/logic/81-quickpick.sqllogic +24 -0
- package/test/logic/82-bloom-join.sqllogic +185 -0
- package/test/logic/83-merge-join.sqllogic +280 -0
- package/test/logic/84-asof-scan.sqllogic +238 -0
- package/test/logic/84-key-cardinality.sqllogic +50 -0
- package/test/logic/85-relational-const-folding.sqllogic +51 -0
- package/test/logic/86-scalar-cse.sqllogic +42 -0
- package/test/logic/90-error_paths.sqllogic +114 -0
- package/test/logic/90.1-parse-errors.sqllogic +77 -0
- package/test/logic/90.2-alter-table-errors.sqllogic +53 -0
- package/test/logic/90.2.1-alter-extra-errors.sqllogic +51 -0
- package/test/logic/90.3-expression-errors.sqllogic +43 -0
- package/test/logic/90.4-dml-errors.sqllogic +47 -0
- package/test/logic/90.5-unsupported-join-types.sqllogic +37 -0
- package/test/logic/90.5.1-right-full-join-read.sqllogic +157 -0
- package/test/logic/90.6-select-error-paths.sqllogic +57 -0
- package/test/logic/91-merge-join-edge-cases.sqllogic +159 -0
- package/test/logic/92-hash-aggregate-edge-cases.sqllogic +139 -0
- package/test/logic/93-ddl-view-edge-cases.sqllogic +104 -0
- package/test/logic/93.1-view-error-paths.sqllogic +74 -0
- package/test/logic/93.2-view-mutation-pending.sqllogic +108 -0
- package/test/logic/93.3-view-mutation-or-clause.sqllogic +60 -0
- package/test/logic/93.4-view-mutation.sqllogic +4881 -0
- package/test/logic/93.4.1-view-mutation-partial-unique-cardinality.sqllogic +31 -0
- package/test/logic/93.5-authored-inverse.sqllogic +415 -0
- package/test/logic/93.6-set-op-flagless-write.sqllogic +862 -0
- package/test/logic/94-tvf-edge-cases.sqllogic +58 -0
- package/test/logic/94.1-limit-offset-edge-cases.sqllogic +109 -0
- package/test/logic/94.2-limit-offset-extras.sqllogic +98 -0
- package/test/logic/95-assertions.sqllogic +1417 -0
- package/test/logic/96-subquery-edge-cases.sqllogic +276 -0
- package/test/logic/97-json-function-edge-cases.sqllogic +633 -0
- package/test/logic/97.1-json-blob-and-special-values.sqllogic +92 -0
- package/test/logic/98-temporal-edge-cases.sqllogic +515 -0
- package/test/logic/99-conversion-edge-cases.sqllogic +325 -0
- package/test/logic/99.0.1-schema-index-listing.sqllogic +21 -0
- package/test/logic/99.1-cast-syntax-extras.sqllogic +167 -0
- package/test/logic/drop-unique-index.sqllogic +88 -0
- package/test/logic/pushdown-test.sqllogic +36 -0
|
@@ -0,0 +1,1941 @@
|
|
|
1
|
+
-- Window Function Tests
|
|
2
|
+
-- Tests for SQL window functions with OVER clauses
|
|
3
|
+
|
|
4
|
+
-- Setup test data
|
|
5
|
+
CREATE TABLE sales (
|
|
6
|
+
id INTEGER PRIMARY KEY,
|
|
7
|
+
product TEXT,
|
|
8
|
+
region TEXT,
|
|
9
|
+
amount NUMERIC
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
INSERT INTO sales VALUES
|
|
13
|
+
(1, 'Widget', 'North', 100.50),
|
|
14
|
+
(2, 'Gadget', 'North', 75.25),
|
|
15
|
+
(3, 'Widget', 'South', 120.75),
|
|
16
|
+
(4, 'Gadget', 'South', 90.00),
|
|
17
|
+
(5, 'Widget', 'North', 85.50),
|
|
18
|
+
(6, 'Gadget', 'West', 110.25);
|
|
19
|
+
|
|
20
|
+
-- Basic ROW_NUMBER() without partitioning
|
|
21
|
+
SELECT id, product, ROW_NUMBER() OVER (ORDER BY id) as rn
|
|
22
|
+
FROM sales ORDER BY id;
|
|
23
|
+
→ [{"id":1,"product":"Widget","rn":1},{"id":2,"product":"Gadget","rn":2},{"id":3,"product":"Widget","rn":3},{"id":4,"product":"Gadget","rn":4},{"id":5,"product":"Widget","rn":5},{"id":6,"product":"Gadget","rn":6}]
|
|
24
|
+
|
|
25
|
+
-- ROW_NUMBER() with PARTITION BY
|
|
26
|
+
SELECT product, region, amount, ROW_NUMBER() OVER (PARTITION BY product ORDER BY amount) as rn
|
|
27
|
+
FROM sales ORDER BY product, amount;
|
|
28
|
+
→ [{"product":"Gadget","region":"North","amount":75.25,"rn":1},{"product":"Gadget","region":"South","amount":90.00,"rn":2},{"product":"Gadget","region":"West","amount":110.25,"rn":3},{"product":"Widget","region":"North","amount":85.50,"rn":1},{"product":"Widget","region":"North","amount":100.50,"rn":2},{"product":"Widget","region":"South","amount":120.75,"rn":3}]
|
|
29
|
+
|
|
30
|
+
-- Test RANK() function
|
|
31
|
+
SELECT product, amount, RANK() OVER (PARTITION BY product ORDER BY amount) as rnk
|
|
32
|
+
FROM sales ORDER BY product, amount;
|
|
33
|
+
→ [{"product":"Gadget","amount":75.25,"rnk":1},{"product":"Gadget","amount":90.00,"rnk":2},{"product":"Gadget","amount":110.25,"rnk":3},{"product":"Widget","amount":85.50,"rnk":1},{"product":"Widget","amount":100.50,"rnk":2},{"product":"Widget","amount":120.75,"rnk":3}]
|
|
34
|
+
|
|
35
|
+
-- Test DENSE_RANK() function
|
|
36
|
+
SELECT product, amount, DENSE_RANK() OVER (PARTITION BY product ORDER BY amount) as dense_rnk
|
|
37
|
+
FROM sales ORDER BY product, amount;
|
|
38
|
+
→ [{"product":"Gadget","amount":75.25,"dense_rnk":1},{"product":"Gadget","amount":90.00,"dense_rnk":2},{"product":"Gadget","amount":110.25,"dense_rnk":3},{"product":"Widget","amount":85.50,"dense_rnk":1},{"product":"Widget","amount":100.50,"dense_rnk":2},{"product":"Widget","amount":120.75,"dense_rnk":3}]
|
|
39
|
+
|
|
40
|
+
-- Test SUM() OVER for running totals
|
|
41
|
+
SELECT product, amount, SUM(amount) OVER (PARTITION BY product ORDER BY id ROWS UNBOUNDED PRECEDING) as running_sum
|
|
42
|
+
FROM sales ORDER BY product, id;
|
|
43
|
+
→ [{"product":"Gadget","amount":75.25,"running_sum":75.25},{"product":"Gadget","amount":90.00,"running_sum":165.25},{"product":"Gadget","amount":110.25,"running_sum":275.50},{"product":"Widget","amount":100.50,"running_sum":100.50},{"product":"Widget","amount":120.75,"running_sum":221.25},{"product":"Widget","amount":85.50,"running_sum":306.75}]
|
|
44
|
+
|
|
45
|
+
-- Test COUNT() OVER for row counting
|
|
46
|
+
SELECT region, product, COUNT(*) OVER (PARTITION BY region ORDER BY id ROWS UNBOUNDED PRECEDING) as cumulative_count
|
|
47
|
+
FROM sales ORDER BY region, id;
|
|
48
|
+
→ [{"region":"North","product":"Widget","cumulative_count":1},{"region":"North","product":"Gadget","cumulative_count":2},{"region":"North","product":"Widget","cumulative_count":3},{"region":"South","product":"Widget","cumulative_count":1},{"region":"South","product":"Gadget","cumulative_count":2},{"region":"West","product":"Gadget","cumulative_count":1}]
|
|
49
|
+
|
|
50
|
+
-- Test AVG() OVER for running averages
|
|
51
|
+
SELECT product, amount, AVG(amount) OVER (PARTITION BY product ORDER BY id ROWS UNBOUNDED PRECEDING) as running_avg
|
|
52
|
+
FROM sales ORDER BY product, id;
|
|
53
|
+
→ [{"product":"Gadget","amount":75.25,"running_avg":75.25},{"product":"Gadget","amount":90.00,"running_avg":82.625},{"product":"Gadget","amount":110.25,"running_avg":91.83333333333333},{"product":"Widget","amount":100.50,"running_avg":100.50},{"product":"Widget","amount":120.75,"running_avg":110.625},{"product":"Widget","amount":85.50,"running_avg":102.25}]
|
|
54
|
+
|
|
55
|
+
-- Test multiple window functions in same query
|
|
56
|
+
SELECT
|
|
57
|
+
product,
|
|
58
|
+
region,
|
|
59
|
+
amount,
|
|
60
|
+
ROW_NUMBER() OVER (ORDER BY amount DESC) as overall_rank,
|
|
61
|
+
ROW_NUMBER() OVER (PARTITION BY product ORDER BY amount DESC) as product_rank,
|
|
62
|
+
SUM(amount) OVER (PARTITION BY product) as product_total
|
|
63
|
+
FROM sales ORDER BY amount DESC;
|
|
64
|
+
→ [{"product":"Widget","region":"South","amount":120.75,"overall_rank":1,"product_rank":1,"product_total":306.75},{"product":"Gadget","region":"West","amount":110.25,"overall_rank":2,"product_rank":1,"product_total":275.50},{"product":"Widget","region":"North","amount":100.50,"overall_rank":3,"product_rank":2,"product_total":306.75},{"product":"Gadget","region":"South","amount":90.00,"overall_rank":4,"product_rank":2,"product_total":275.50},{"product":"Widget","region":"North","amount":85.50,"overall_rank":5,"product_rank":3,"product_total":306.75},{"product":"Gadget","region":"North","amount":75.25,"overall_rank":6,"product_rank":3,"product_total":275.50}]
|
|
65
|
+
|
|
66
|
+
-- Test window functions with different frame specifications
|
|
67
|
+
SELECT id, amount, SUM(amount) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as windowed_sum
|
|
68
|
+
FROM sales ORDER BY id;
|
|
69
|
+
→ [{"id":1,"amount":100.50,"windowed_sum":175.75},{"id":2,"amount":75.25,"windowed_sum":296.50},{"id":3,"amount":120.75,"windowed_sum":286.00},{"id":4,"amount":90.00,"windowed_sum":296.25},{"id":5,"amount":85.50,"windowed_sum":285.75},{"id":6,"amount":110.25,"windowed_sum":195.75}]
|
|
70
|
+
|
|
71
|
+
-- Test window frames with non-1 literal offsets
|
|
72
|
+
SELECT id, amount, SUM(amount) OVER (ORDER BY id ROWS BETWEEN 2 PRECEDING AND 1 FOLLOWING) as windowed_sum
|
|
73
|
+
FROM sales ORDER BY id;
|
|
74
|
+
→ [{"id":1,"amount":100.50,"windowed_sum":175.75},{"id":2,"amount":75.25,"windowed_sum":296.50},{"id":3,"amount":120.75,"windowed_sum":386.50},{"id":4,"amount":90.00,"windowed_sum":371.50},{"id":5,"amount":85.50,"windowed_sum":406.50},{"id":6,"amount":110.25,"windowed_sum":285.75}]
|
|
75
|
+
|
|
76
|
+
-- Test multiple window functions with same OVER clause
|
|
77
|
+
SELECT product, amount,
|
|
78
|
+
ROW_NUMBER() OVER (PARTITION BY product ORDER BY amount) as rn,
|
|
79
|
+
RANK() OVER (PARTITION BY product ORDER BY amount) as rnk,
|
|
80
|
+
COUNT(*) OVER (PARTITION BY product ORDER BY amount ROWS UNBOUNDED PRECEDING) as cnt
|
|
81
|
+
FROM sales ORDER BY product, amount;
|
|
82
|
+
→ [{"product":"Gadget","amount":75.25,"rn":1,"rnk":1,"cnt":1},{"product":"Gadget","amount":90.00,"rn":2,"rnk":2,"cnt":2},{"product":"Gadget","amount":110.25,"rn":3,"rnk":3,"cnt":3},{"product":"Widget","amount":85.50,"rn":1,"rnk":1,"cnt":1},{"product":"Widget","amount":100.50,"rn":2,"rnk":2,"cnt":2},{"product":"Widget","amount":120.75,"rn":3,"rnk":3,"cnt":3}]
|
|
83
|
+
|
|
84
|
+
-- Edge case: Window function with NULL handling
|
|
85
|
+
CREATE TABLE sales_with_nulls (
|
|
86
|
+
id INTEGER PRIMARY KEY,
|
|
87
|
+
product TEXT,
|
|
88
|
+
amount NUMERIC NULL
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
INSERT INTO sales_with_nulls VALUES
|
|
92
|
+
(1, 'Widget', 100.00),
|
|
93
|
+
(2, 'Widget', NULL),
|
|
94
|
+
(3, 'Gadget', 75.00),
|
|
95
|
+
(4, 'Widget', 120.00),
|
|
96
|
+
(5, 'Gadget', NULL);
|
|
97
|
+
|
|
98
|
+
SELECT product, amount, ROW_NUMBER() OVER (PARTITION BY product ORDER BY amount NULLS LAST) as rn
|
|
99
|
+
FROM sales_with_nulls ORDER BY product, COALESCE(amount, 999999);
|
|
100
|
+
→ [{"product":"Gadget","amount":75.00,"rn":1},{"product":"Gadget","amount":null,"rn":2},{"product":"Widget","amount":100.00,"rn":1},{"product":"Widget","amount":120.00,"rn":2},{"product":"Widget","amount":null,"rn":3}]
|
|
101
|
+
|
|
102
|
+
-- Test aggregate window functions with NULLs
|
|
103
|
+
SELECT product, amount,
|
|
104
|
+
COUNT(*) OVER (PARTITION BY product) as total_rows,
|
|
105
|
+
COUNT(amount) OVER (PARTITION BY product) as non_null_amounts,
|
|
106
|
+
SUM(amount) OVER (PARTITION BY product) as total_amount
|
|
107
|
+
FROM sales_with_nulls ORDER BY product, COALESCE(amount, 999999);
|
|
108
|
+
→ [{"product":"Gadget","amount":75.00,"total_rows":2,"non_null_amounts":1,"total_amount":75.00},{"product":"Gadget","amount":null,"total_rows":2,"non_null_amounts":1,"total_amount":75.00},{"product":"Widget","amount":100.00,"total_rows":3,"non_null_amounts":2,"total_amount":220.00},{"product":"Widget","amount":120.00,"total_rows":3,"non_null_amounts":2,"total_amount":220.00},{"product":"Widget","amount":null,"total_rows":3,"non_null_amounts":2,"total_amount":220.00}]
|
|
109
|
+
|
|
110
|
+
DROP TABLE sales;
|
|
111
|
+
DROP TABLE sales_with_nulls;
|
|
112
|
+
|
|
113
|
+
-- Window Function Frame Equivalence Tests
|
|
114
|
+
CREATE TABLE frame_test (id INTEGER PRIMARY KEY, value INTEGER, grp INTEGER);
|
|
115
|
+
INSERT INTO frame_test VALUES
|
|
116
|
+
(1, 10, 1), (2, 20, 1), (3, 30, 1),
|
|
117
|
+
(4, 15, 2), (5, 25, 2), (6, 35, 2);
|
|
118
|
+
|
|
119
|
+
-- Test equivalent window frame specifications produce same results
|
|
120
|
+
SELECT value, grp,
|
|
121
|
+
SUM(value) OVER (PARTITION BY grp ORDER BY value ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as running_sum
|
|
122
|
+
FROM frame_test ORDER BY grp, value;
|
|
123
|
+
→ [{"value":10,"grp":1,"running_sum":10},{"value":20,"grp":1,"running_sum":30},{"value":30,"grp":1,"running_sum":60},{"value":15,"grp":2,"running_sum":15},{"value":25,"grp":2,"running_sum":40},{"value":35,"grp":2,"running_sum":75}]
|
|
124
|
+
|
|
125
|
+
SELECT value, grp,
|
|
126
|
+
SUM(value) OVER (PARTITION BY grp ORDER BY value ROWS UNBOUNDED PRECEDING) as running_sum
|
|
127
|
+
FROM frame_test ORDER BY grp, value;
|
|
128
|
+
→ [{"value":10,"grp":1,"running_sum":10},{"value":20,"grp":1,"running_sum":30},{"value":30,"grp":1,"running_sum":60},{"value":15,"grp":2,"running_sum":15},{"value":25,"grp":2,"running_sum":40},{"value":35,"grp":2,"running_sum":75}]
|
|
129
|
+
|
|
130
|
+
-- Test different but equivalent frame specifications for COUNT
|
|
131
|
+
SELECT value, grp,
|
|
132
|
+
COUNT(*) OVER (PARTITION BY grp ORDER BY value ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as running_count
|
|
133
|
+
FROM frame_test ORDER BY grp, value;
|
|
134
|
+
→ [{"value":10,"grp":1,"running_count":1},{"value":20,"grp":1,"running_count":2},{"value":30,"grp":1,"running_count":3},{"value":15,"grp":2,"running_count":1},{"value":25,"grp":2,"running_count":2},{"value":35,"grp":2,"running_count":3}]
|
|
135
|
+
|
|
136
|
+
SELECT value, grp,
|
|
137
|
+
COUNT(*) OVER (PARTITION BY grp ORDER BY value ROWS UNBOUNDED PRECEDING) as running_count
|
|
138
|
+
FROM frame_test ORDER BY grp, value;
|
|
139
|
+
→ [{"value":10,"grp":1,"running_count":1},{"value":20,"grp":1,"running_count":2},{"value":30,"grp":1,"running_count":3},{"value":15,"grp":2,"running_count":1},{"value":25,"grp":2,"running_count":2},{"value":35,"grp":2,"running_count":3}]
|
|
140
|
+
|
|
141
|
+
DROP TABLE frame_test;
|
|
142
|
+
|
|
143
|
+
-- =============================================================================
|
|
144
|
+
-- Window PARTITION BY with collation tests
|
|
145
|
+
-- =============================================================================
|
|
146
|
+
|
|
147
|
+
-- NOCASE collation: PARTITION BY should group case-insensitively
|
|
148
|
+
CREATE TABLE win_nocase (
|
|
149
|
+
id INTEGER PRIMARY KEY,
|
|
150
|
+
category TEXT COLLATE NOCASE,
|
|
151
|
+
val INTEGER
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
INSERT INTO win_nocase VALUES
|
|
155
|
+
(1, 'Alpha', 10),
|
|
156
|
+
(2, 'ALPHA', 20),
|
|
157
|
+
(3, 'alpha', 30),
|
|
158
|
+
(4, 'Beta', 40),
|
|
159
|
+
(5, 'BETA', 50);
|
|
160
|
+
|
|
161
|
+
-- All 'alpha' variants should be in the same partition (3 rows), 'beta' variants together (2 rows)
|
|
162
|
+
SELECT id, category, val,
|
|
163
|
+
ROW_NUMBER() OVER (PARTITION BY category ORDER BY id) as rn,
|
|
164
|
+
COUNT(*) OVER (PARTITION BY category) as partition_size
|
|
165
|
+
FROM win_nocase ORDER BY id;
|
|
166
|
+
→ [{"id":1,"category":"Alpha","val":10,"rn":1,"partition_size":3},{"id":2,"category":"ALPHA","val":20,"rn":2,"partition_size":3},{"id":3,"category":"alpha","val":30,"rn":3,"partition_size":3},{"id":4,"category":"Beta","val":40,"rn":1,"partition_size":2},{"id":5,"category":"BETA","val":50,"rn":2,"partition_size":2}]
|
|
167
|
+
|
|
168
|
+
-- SUM within NOCASE partition
|
|
169
|
+
SELECT id, category, val,
|
|
170
|
+
SUM(val) OVER (PARTITION BY category) as partition_total
|
|
171
|
+
FROM win_nocase ORDER BY id;
|
|
172
|
+
→ [{"id":1,"category":"Alpha","val":10,"partition_total":60},{"id":2,"category":"ALPHA","val":20,"partition_total":60},{"id":3,"category":"alpha","val":30,"partition_total":60},{"id":4,"category":"Beta","val":40,"partition_total":90},{"id":5,"category":"BETA","val":50,"partition_total":90}]
|
|
173
|
+
|
|
174
|
+
-- DENSE_RANK with NOCASE ORDER BY
|
|
175
|
+
CREATE TABLE win_rank_nocase (
|
|
176
|
+
id INTEGER PRIMARY KEY,
|
|
177
|
+
name TEXT COLLATE NOCASE,
|
|
178
|
+
score INTEGER
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
INSERT INTO win_rank_nocase VALUES
|
|
182
|
+
(1, 'Alice', 100),
|
|
183
|
+
(2, 'ALICE', 100),
|
|
184
|
+
(3, 'Bob', 90),
|
|
185
|
+
(4, 'Charlie', 80);
|
|
186
|
+
|
|
187
|
+
-- 'Alice' and 'ALICE' should be considered equal for ranking under NOCASE
|
|
188
|
+
SELECT id, name, score,
|
|
189
|
+
DENSE_RANK() OVER (ORDER BY name) as drnk
|
|
190
|
+
FROM win_rank_nocase ORDER BY id;
|
|
191
|
+
→ [{"id":1,"name":"Alice","score":100,"drnk":1},{"id":2,"name":"ALICE","score":100,"drnk":1},{"id":3,"name":"Bob","score":90,"drnk":2},{"id":4,"name":"Charlie","score":80,"drnk":3}]
|
|
192
|
+
|
|
193
|
+
-- RANK with NOCASE ORDER BY
|
|
194
|
+
SELECT id, name, score,
|
|
195
|
+
RANK() OVER (ORDER BY name) as rnk
|
|
196
|
+
FROM win_rank_nocase ORDER BY id;
|
|
197
|
+
→ [{"id":1,"name":"Alice","score":100,"rnk":1},{"id":2,"name":"ALICE","score":100,"rnk":1},{"id":3,"name":"Bob","score":90,"rnk":3},{"id":4,"name":"Charlie","score":80,"rnk":4}]
|
|
198
|
+
|
|
199
|
+
-- NULL in PARTITION BY: NULLs should group together
|
|
200
|
+
CREATE TABLE win_null_part (
|
|
201
|
+
id INTEGER PRIMARY KEY,
|
|
202
|
+
grp TEXT NULL,
|
|
203
|
+
val INTEGER
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
INSERT INTO win_null_part VALUES
|
|
207
|
+
(1, NULL, 10),
|
|
208
|
+
(2, NULL, 20),
|
|
209
|
+
(3, 'A', 30),
|
|
210
|
+
(4, 'A', 40);
|
|
211
|
+
|
|
212
|
+
SELECT id, grp, val,
|
|
213
|
+
COUNT(*) OVER (PARTITION BY grp) as partition_size,
|
|
214
|
+
SUM(val) OVER (PARTITION BY grp) as partition_total
|
|
215
|
+
FROM win_null_part ORDER BY id;
|
|
216
|
+
→ [{"id":1,"grp":null,"val":10,"partition_size":2,"partition_total":30},{"id":2,"grp":null,"val":20,"partition_size":2,"partition_total":30},{"id":3,"grp":"A","val":30,"partition_size":2,"partition_total":70},{"id":4,"grp":"A","val":40,"partition_size":2,"partition_total":70}]
|
|
217
|
+
|
|
218
|
+
DROP TABLE win_nocase;
|
|
219
|
+
DROP TABLE win_rank_nocase;
|
|
220
|
+
DROP TABLE win_null_part;
|
|
221
|
+
|
|
222
|
+
-- =============================================================================
|
|
223
|
+
-- LAG / LEAD tests
|
|
224
|
+
-- =============================================================================
|
|
225
|
+
|
|
226
|
+
CREATE TABLE nav_test (id INTEGER PRIMARY KEY, val INTEGER, grp TEXT);
|
|
227
|
+
INSERT INTO nav_test VALUES
|
|
228
|
+
(1, 10, 'A'), (2, 20, 'A'), (3, 30, 'A'),
|
|
229
|
+
(4, 100, 'B'), (5, 200, 'B');
|
|
230
|
+
|
|
231
|
+
-- Basic LAG (default offset = 1)
|
|
232
|
+
SELECT id, val, LAG(val) OVER (ORDER BY id) as prev_val
|
|
233
|
+
FROM nav_test ORDER BY id;
|
|
234
|
+
→ [{"id":1,"val":10,"prev_val":null},{"id":2,"val":20,"prev_val":10},{"id":3,"val":30,"prev_val":20},{"id":4,"val":100,"prev_val":30},{"id":5,"val":200,"prev_val":100}]
|
|
235
|
+
|
|
236
|
+
-- Basic LEAD (default offset = 1)
|
|
237
|
+
SELECT id, val, LEAD(val) OVER (ORDER BY id) as next_val
|
|
238
|
+
FROM nav_test ORDER BY id;
|
|
239
|
+
→ [{"id":1,"val":10,"next_val":20},{"id":2,"val":20,"next_val":30},{"id":3,"val":30,"next_val":100},{"id":4,"val":100,"next_val":200},{"id":5,"val":200,"next_val":null}]
|
|
240
|
+
|
|
241
|
+
-- LAG with explicit offset
|
|
242
|
+
SELECT id, val, LAG(val, 2) OVER (ORDER BY id) as prev2_val
|
|
243
|
+
FROM nav_test ORDER BY id;
|
|
244
|
+
→ [{"id":1,"val":10,"prev2_val":null},{"id":2,"val":20,"prev2_val":null},{"id":3,"val":30,"prev2_val":10},{"id":4,"val":100,"prev2_val":20},{"id":5,"val":200,"prev2_val":30}]
|
|
245
|
+
|
|
246
|
+
-- LEAD with explicit offset and default
|
|
247
|
+
SELECT id, val, LEAD(val, 1, -1) OVER (ORDER BY id) as next_val
|
|
248
|
+
FROM nav_test ORDER BY id;
|
|
249
|
+
→ [{"id":1,"val":10,"next_val":20},{"id":2,"val":20,"next_val":30},{"id":3,"val":30,"next_val":100},{"id":4,"val":100,"next_val":200},{"id":5,"val":200,"next_val":-1}]
|
|
250
|
+
|
|
251
|
+
-- LAG with default value
|
|
252
|
+
SELECT id, val, LAG(val, 1, 0) OVER (ORDER BY id) as prev_val
|
|
253
|
+
FROM nav_test ORDER BY id;
|
|
254
|
+
→ [{"id":1,"val":10,"prev_val":0},{"id":2,"val":20,"prev_val":10},{"id":3,"val":30,"prev_val":20},{"id":4,"val":100,"prev_val":30},{"id":5,"val":200,"prev_val":100}]
|
|
255
|
+
|
|
256
|
+
-- LAG/LEAD with PARTITION BY (should not cross partition boundaries)
|
|
257
|
+
SELECT id, val, grp,
|
|
258
|
+
LAG(val) OVER (PARTITION BY grp ORDER BY id) as prev_val,
|
|
259
|
+
LEAD(val) OVER (PARTITION BY grp ORDER BY id) as next_val
|
|
260
|
+
FROM nav_test ORDER BY id;
|
|
261
|
+
→ [{"id":1,"val":10,"grp":"A","prev_val":null,"next_val":20},{"id":2,"val":20,"grp":"A","prev_val":10,"next_val":30},{"id":3,"val":30,"grp":"A","prev_val":20,"next_val":null},{"id":4,"val":100,"grp":"B","prev_val":null,"next_val":200},{"id":5,"val":200,"grp":"B","prev_val":100,"next_val":null}]
|
|
262
|
+
|
|
263
|
+
DROP TABLE nav_test;
|
|
264
|
+
|
|
265
|
+
-- =============================================================================
|
|
266
|
+
-- FIRST_VALUE / LAST_VALUE tests
|
|
267
|
+
-- =============================================================================
|
|
268
|
+
|
|
269
|
+
CREATE TABLE fv_test (id INTEGER PRIMARY KEY, val INTEGER, grp TEXT);
|
|
270
|
+
INSERT INTO fv_test VALUES
|
|
271
|
+
(1, 10, 'A'), (2, 20, 'A'), (3, 30, 'A'),
|
|
272
|
+
(4, 100, 'B'), (5, 200, 'B');
|
|
273
|
+
|
|
274
|
+
-- FIRST_VALUE over entire partition
|
|
275
|
+
SELECT id, val, grp,
|
|
276
|
+
FIRST_VALUE(val) OVER (PARTITION BY grp ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as first_val
|
|
277
|
+
FROM fv_test ORDER BY id;
|
|
278
|
+
→ [{"id":1,"val":10,"grp":"A","first_val":10},{"id":2,"val":20,"grp":"A","first_val":10},{"id":3,"val":30,"grp":"A","first_val":10},{"id":4,"val":100,"grp":"B","first_val":100},{"id":5,"val":200,"grp":"B","first_val":100}]
|
|
279
|
+
|
|
280
|
+
-- LAST_VALUE over entire partition (must use explicit frame)
|
|
281
|
+
SELECT id, val, grp,
|
|
282
|
+
LAST_VALUE(val) OVER (PARTITION BY grp ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as last_val
|
|
283
|
+
FROM fv_test ORDER BY id;
|
|
284
|
+
→ [{"id":1,"val":10,"grp":"A","last_val":30},{"id":2,"val":20,"grp":"A","last_val":30},{"id":3,"val":30,"grp":"A","last_val":30},{"id":4,"val":100,"grp":"B","last_val":200},{"id":5,"val":200,"grp":"B","last_val":200}]
|
|
285
|
+
|
|
286
|
+
-- LAST_VALUE with default frame (ORDER BY present, no explicit frame = up to current row)
|
|
287
|
+
SELECT id, val,
|
|
288
|
+
LAST_VALUE(val) OVER (ORDER BY id) as last_val
|
|
289
|
+
FROM fv_test ORDER BY id;
|
|
290
|
+
→ [{"id":1,"val":10,"last_val":10},{"id":2,"val":20,"last_val":20},{"id":3,"val":30,"last_val":30},{"id":4,"val":100,"last_val":100},{"id":5,"val":200,"last_val":200}]
|
|
291
|
+
|
|
292
|
+
-- FIRST_VALUE without ORDER BY (entire partition)
|
|
293
|
+
SELECT id, val, grp,
|
|
294
|
+
FIRST_VALUE(val) OVER (PARTITION BY grp) as first_val
|
|
295
|
+
FROM fv_test ORDER BY id;
|
|
296
|
+
→ [{"id":1,"val":10,"grp":"A","first_val":10},{"id":2,"val":20,"grp":"A","first_val":10},{"id":3,"val":30,"grp":"A","first_val":10},{"id":4,"val":100,"grp":"B","first_val":100},{"id":5,"val":200,"grp":"B","first_val":100}]
|
|
297
|
+
|
|
298
|
+
DROP TABLE fv_test;
|
|
299
|
+
|
|
300
|
+
-- =============================================================================
|
|
301
|
+
-- PERCENT_RANK / CUME_DIST tests
|
|
302
|
+
-- =============================================================================
|
|
303
|
+
|
|
304
|
+
CREATE TABLE stat_test (id INTEGER PRIMARY KEY, val INTEGER);
|
|
305
|
+
INSERT INTO stat_test VALUES (1, 10), (2, 20), (3, 20), (4, 30), (5, 40);
|
|
306
|
+
|
|
307
|
+
-- PERCENT_RANK: (rank - 1) / (rows - 1)
|
|
308
|
+
SELECT id, val, PERCENT_RANK() OVER (ORDER BY val) as pct_rank
|
|
309
|
+
FROM stat_test ORDER BY id;
|
|
310
|
+
→ [{"id":1,"val":10,"pct_rank":0},{"id":2,"val":20,"pct_rank":0.25},{"id":3,"val":20,"pct_rank":0.25},{"id":4,"val":30,"pct_rank":0.75},{"id":5,"val":40,"pct_rank":1}]
|
|
311
|
+
|
|
312
|
+
-- CUME_DIST: (count of rows <= current) / total
|
|
313
|
+
SELECT id, val, CUME_DIST() OVER (ORDER BY val) as cume
|
|
314
|
+
FROM stat_test ORDER BY id;
|
|
315
|
+
→ [{"id":1,"val":10,"cume":0.2},{"id":2,"val":20,"cume":0.6},{"id":3,"val":20,"cume":0.6},{"id":4,"val":30,"cume":0.8},{"id":5,"val":40,"cume":1}]
|
|
316
|
+
|
|
317
|
+
-- Single-row partition: PERCENT_RANK = 0, CUME_DIST = 1
|
|
318
|
+
SELECT val, PERCENT_RANK() OVER (PARTITION BY val ORDER BY val) as pct_rank,
|
|
319
|
+
CUME_DIST() OVER (PARTITION BY val ORDER BY val) as cume
|
|
320
|
+
FROM stat_test WHERE val = 10;
|
|
321
|
+
→ [{"val":10,"pct_rank":0,"cume":1}]
|
|
322
|
+
|
|
323
|
+
DROP TABLE stat_test;
|
|
324
|
+
|
|
325
|
+
-- =============================================================================
|
|
326
|
+
-- NTILE tests
|
|
327
|
+
-- =============================================================================
|
|
328
|
+
|
|
329
|
+
CREATE TABLE ntile_test (id INTEGER PRIMARY KEY, val INTEGER);
|
|
330
|
+
INSERT INTO ntile_test VALUES (1, 10), (2, 20), (3, 30), (4, 40), (5, 50);
|
|
331
|
+
|
|
332
|
+
-- NTILE(2): 3 rows in bucket 1, 2 rows in bucket 2
|
|
333
|
+
SELECT id, val, NTILE(2) OVER (ORDER BY id) as bucket
|
|
334
|
+
FROM ntile_test ORDER BY id;
|
|
335
|
+
→ [{"id":1,"val":10,"bucket":1},{"id":2,"val":20,"bucket":1},{"id":3,"val":30,"bucket":1},{"id":4,"val":40,"bucket":2},{"id":5,"val":50,"bucket":2}]
|
|
336
|
+
|
|
337
|
+
-- NTILE(3): 2 rows in bucket 1, 2 in bucket 2, 1 in bucket 3
|
|
338
|
+
SELECT id, val, NTILE(3) OVER (ORDER BY id) as bucket
|
|
339
|
+
FROM ntile_test ORDER BY id;
|
|
340
|
+
→ [{"id":1,"val":10,"bucket":1},{"id":2,"val":20,"bucket":1},{"id":3,"val":30,"bucket":2},{"id":4,"val":40,"bucket":2},{"id":5,"val":50,"bucket":3}]
|
|
341
|
+
|
|
342
|
+
-- NTILE(5): 1 row per bucket
|
|
343
|
+
SELECT id, val, NTILE(5) OVER (ORDER BY id) as bucket
|
|
344
|
+
FROM ntile_test ORDER BY id;
|
|
345
|
+
→ [{"id":1,"val":10,"bucket":1},{"id":2,"val":20,"bucket":2},{"id":3,"val":30,"bucket":3},{"id":4,"val":40,"bucket":4},{"id":5,"val":50,"bucket":5}]
|
|
346
|
+
|
|
347
|
+
DROP TABLE ntile_test;
|
|
348
|
+
|
|
349
|
+
-- =============================================================================
|
|
350
|
+
-- RANGE BETWEEN tests (value-based window frames)
|
|
351
|
+
-- =============================================================================
|
|
352
|
+
|
|
353
|
+
CREATE TABLE range_test (id INTEGER PRIMARY KEY, val INTEGER, grp TEXT);
|
|
354
|
+
INSERT INTO range_test VALUES
|
|
355
|
+
(1, 10, 'A'), (2, 20, 'A'), (3, 20, 'A'), (4, 30, 'A'), (5, 50, 'A');
|
|
356
|
+
|
|
357
|
+
-- RANGE CURRENT ROW: includes all peer rows (same ORDER BY value)
|
|
358
|
+
SELECT id, val,
|
|
359
|
+
COUNT(*) OVER (ORDER BY val RANGE CURRENT ROW) as peer_count
|
|
360
|
+
FROM range_test ORDER BY id;
|
|
361
|
+
→ [{"id":1,"val":10,"peer_count":1},{"id":2,"val":20,"peer_count":2},{"id":3,"val":20,"peer_count":2},{"id":4,"val":30,"peer_count":1},{"id":5,"val":50,"peer_count":1}]
|
|
362
|
+
|
|
363
|
+
-- RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW (default frame with ORDER BY)
|
|
364
|
+
-- Should include all peers at current value
|
|
365
|
+
SELECT id, val,
|
|
366
|
+
SUM(val) OVER (ORDER BY val RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as running_sum
|
|
367
|
+
FROM range_test ORDER BY id;
|
|
368
|
+
→ [{"id":1,"val":10,"running_sum":10},{"id":2,"val":20,"running_sum":50},{"id":3,"val":20,"running_sum":50},{"id":4,"val":30,"running_sum":80},{"id":5,"val":50,"running_sum":130}]
|
|
369
|
+
|
|
370
|
+
-- Default frame (ORDER BY present) should be RANGE UNBOUNDED PRECEDING AND CURRENT ROW
|
|
371
|
+
-- including peers
|
|
372
|
+
SELECT id, val,
|
|
373
|
+
SUM(val) OVER (ORDER BY val) as running_sum
|
|
374
|
+
FROM range_test ORDER BY id;
|
|
375
|
+
→ [{"id":1,"val":10,"running_sum":10},{"id":2,"val":20,"running_sum":50},{"id":3,"val":20,"running_sum":50},{"id":4,"val":30,"running_sum":80},{"id":5,"val":50,"running_sum":130}]
|
|
376
|
+
|
|
377
|
+
-- RANGE N PRECEDING: value-based offset
|
|
378
|
+
SELECT id, val,
|
|
379
|
+
SUM(val) OVER (ORDER BY val RANGE BETWEEN 10 PRECEDING AND CURRENT ROW) as range_sum
|
|
380
|
+
FROM range_test ORDER BY id;
|
|
381
|
+
→ [{"id":1,"val":10,"range_sum":10},{"id":2,"val":20,"range_sum":50},{"id":3,"val":20,"range_sum":50},{"id":4,"val":30,"range_sum":70},{"id":5,"val":50,"range_sum":50}]
|
|
382
|
+
|
|
383
|
+
-- RANGE BETWEEN N PRECEDING AND N FOLLOWING
|
|
384
|
+
-- val=10: [5,15] → 10; val=20: [15,25] → 20+20=40; val=30: [25,35] → 30; val=50: [45,55] → 50
|
|
385
|
+
SELECT id, val,
|
|
386
|
+
SUM(val) OVER (ORDER BY val RANGE BETWEEN 5 PRECEDING AND 5 FOLLOWING) as range_sum
|
|
387
|
+
FROM range_test ORDER BY id;
|
|
388
|
+
→ [{"id":1,"val":10,"range_sum":10},{"id":2,"val":20,"range_sum":40},{"id":3,"val":20,"range_sum":40},{"id":4,"val":30,"range_sum":30},{"id":5,"val":50,"range_sum":50}]
|
|
389
|
+
|
|
390
|
+
-- RANGE UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING (same as ROWS)
|
|
391
|
+
SELECT id, val,
|
|
392
|
+
SUM(val) OVER (ORDER BY val RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as total
|
|
393
|
+
FROM range_test ORDER BY id;
|
|
394
|
+
→ [{"id":1,"val":10,"total":130},{"id":2,"val":20,"total":130},{"id":3,"val":20,"total":130},{"id":4,"val":30,"total":130},{"id":5,"val":50,"total":130}]
|
|
395
|
+
|
|
396
|
+
-- FIRST_VALUE/LAST_VALUE with RANGE CURRENT ROW
|
|
397
|
+
SELECT id, val,
|
|
398
|
+
FIRST_VALUE(id) OVER (ORDER BY val RANGE CURRENT ROW) as first_peer_id,
|
|
399
|
+
LAST_VALUE(id) OVER (ORDER BY val RANGE CURRENT ROW) as last_peer_id
|
|
400
|
+
FROM range_test ORDER BY id;
|
|
401
|
+
→ [{"id":1,"val":10,"first_peer_id":1,"last_peer_id":1},{"id":2,"val":20,"first_peer_id":2,"last_peer_id":3},{"id":3,"val":20,"first_peer_id":2,"last_peer_id":3},{"id":4,"val":30,"first_peer_id":4,"last_peer_id":4},{"id":5,"val":50,"first_peer_id":5,"last_peer_id":5}]
|
|
402
|
+
|
|
403
|
+
DROP TABLE range_test;
|
|
404
|
+
|
|
405
|
+
-- =============================================================================
|
|
406
|
+
-- Streaming fast-path tests (rule: monotonic-window).
|
|
407
|
+
--
|
|
408
|
+
-- These verify the streaming emitter produces identical output to the buffered
|
|
409
|
+
-- emitter on a variety of monotonic-input cases. The leaf table's PK provides
|
|
410
|
+
-- the monotonicOn(id) advertisement that the rule keys off; queries below
|
|
411
|
+
-- exercise ranking, navigation, and running-aggregate functions over
|
|
412
|
+
-- ORDER BY id.
|
|
413
|
+
-- =============================================================================
|
|
414
|
+
|
|
415
|
+
CREATE TABLE stream_t (id INTEGER PRIMARY KEY, val INTEGER, grp TEXT);
|
|
416
|
+
INSERT INTO stream_t VALUES
|
|
417
|
+
(1, 10, 'A'), (2, 20, 'A'), (3, 30, 'A'),
|
|
418
|
+
(4, 40, 'B'), (5, 50, 'B'), (6, 60, 'B');
|
|
419
|
+
|
|
420
|
+
-- ROW_NUMBER over monotonic PK: streaming.
|
|
421
|
+
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM stream_t ORDER BY id;
|
|
422
|
+
→ [{"id":1,"rn":1},{"id":2,"rn":2},{"id":3,"rn":3},{"id":4,"rn":4},{"id":5,"rn":5},{"id":6,"rn":6}]
|
|
423
|
+
|
|
424
|
+
-- RANK / DENSE_RANK with no peer ties (PK is unique).
|
|
425
|
+
SELECT id, val, RANK() OVER (ORDER BY id) AS r, DENSE_RANK() OVER (ORDER BY id) AS dr
|
|
426
|
+
FROM stream_t ORDER BY id;
|
|
427
|
+
→ [{"id":1,"val":10,"r":1,"dr":1},{"id":2,"val":20,"r":2,"dr":2},{"id":3,"val":30,"r":3,"dr":3},{"id":4,"val":40,"r":4,"dr":4},{"id":5,"val":50,"r":5,"dr":5},{"id":6,"val":60,"r":6,"dr":6}]
|
|
428
|
+
|
|
429
|
+
-- LAG with default offset = 1.
|
|
430
|
+
SELECT id, LAG(val) OVER (ORDER BY id) AS prv FROM stream_t ORDER BY id;
|
|
431
|
+
→ [{"id":1,"prv":null},{"id":2,"prv":10},{"id":3,"prv":20},{"id":4,"prv":30},{"id":5,"prv":40},{"id":6,"prv":50}]
|
|
432
|
+
|
|
433
|
+
-- LAG with literal offset 2 and a non-NULL default.
|
|
434
|
+
SELECT id, LAG(val, 2, -1) OVER (ORDER BY id) AS prv2 FROM stream_t ORDER BY id;
|
|
435
|
+
→ [{"id":1,"prv2":-1},{"id":2,"prv2":-1},{"id":3,"prv2":10},{"id":4,"prv2":20},{"id":5,"prv2":30},{"id":6,"prv2":40}]
|
|
436
|
+
|
|
437
|
+
-- LEAD with default offset and no default value (NULL at boundary).
|
|
438
|
+
SELECT id, LEAD(val) OVER (ORDER BY id) AS nxt FROM stream_t ORDER BY id;
|
|
439
|
+
→ [{"id":1,"nxt":20},{"id":2,"nxt":30},{"id":3,"nxt":40},{"id":4,"nxt":50},{"id":5,"nxt":60},{"id":6,"nxt":null}]
|
|
440
|
+
|
|
441
|
+
-- LEAD with literal offset 1 and a non-NULL default at partition end.
|
|
442
|
+
SELECT id, LEAD(val, 1, -99) OVER (ORDER BY id) AS nxt FROM stream_t ORDER BY id;
|
|
443
|
+
→ [{"id":1,"nxt":20},{"id":2,"nxt":30},{"id":3,"nxt":40},{"id":4,"nxt":50},{"id":5,"nxt":60},{"id":6,"nxt":-99}]
|
|
444
|
+
|
|
445
|
+
-- Running SUM over default RANGE frame (UNBOUNDED PRECEDING TO CURRENT ROW).
|
|
446
|
+
SELECT id, val, SUM(val) OVER (ORDER BY id) AS rs FROM stream_t ORDER BY id;
|
|
447
|
+
→ [{"id":1,"val":10,"rs":10},{"id":2,"val":20,"rs":30},{"id":3,"val":30,"rs":60},{"id":4,"val":40,"rs":100},{"id":5,"val":50,"rs":150},{"id":6,"val":60,"rs":210}]
|
|
448
|
+
|
|
449
|
+
-- Running MIN over explicit ROWS UNBOUNDED PRECEDING.
|
|
450
|
+
SELECT id, val, MIN(val) OVER (ORDER BY id ROWS UNBOUNDED PRECEDING) AS rm FROM stream_t ORDER BY id;
|
|
451
|
+
→ [{"id":1,"val":10,"rm":10},{"id":2,"val":20,"rm":10},{"id":3,"val":30,"rm":10},{"id":4,"val":40,"rm":10},{"id":5,"val":50,"rm":10},{"id":6,"val":60,"rm":10}]
|
|
452
|
+
|
|
453
|
+
-- FIRST_VALUE caches the first row's expr for the partition.
|
|
454
|
+
SELECT id, FIRST_VALUE(val) OVER (ORDER BY id) AS fv FROM stream_t ORDER BY id;
|
|
455
|
+
→ [{"id":1,"fv":10},{"id":2,"fv":10},{"id":3,"fv":10},{"id":4,"fv":10},{"id":5,"fv":10},{"id":6,"fv":10}]
|
|
456
|
+
|
|
457
|
+
-- LAST_VALUE under default frame (== current row in RANGE mode for unique keys).
|
|
458
|
+
SELECT id, LAST_VALUE(val) OVER (ORDER BY id) AS lv FROM stream_t ORDER BY id;
|
|
459
|
+
→ [{"id":1,"lv":10},{"id":2,"lv":20},{"id":3,"lv":30},{"id":4,"lv":40},{"id":5,"lv":50},{"id":6,"lv":60}]
|
|
460
|
+
|
|
461
|
+
-- All-NULL expr column for LAG: defaults bubble through unchanged.
|
|
462
|
+
CREATE TABLE stream_n (id INTEGER PRIMARY KEY, x INTEGER NULL);
|
|
463
|
+
INSERT INTO stream_n VALUES (1, NULL), (2, NULL), (3, NULL);
|
|
464
|
+
SELECT id, LAG(x) OVER (ORDER BY id) AS prv FROM stream_n ORDER BY id;
|
|
465
|
+
→ [{"id":1,"prv":null},{"id":2,"prv":null},{"id":3,"prv":null}]
|
|
466
|
+
|
|
467
|
+
-- Single-row "partition" — running aggregate equals the value itself.
|
|
468
|
+
CREATE TABLE stream_one (id INTEGER PRIMARY KEY, val INTEGER);
|
|
469
|
+
INSERT INTO stream_one VALUES (1, 42);
|
|
470
|
+
SELECT id, val, SUM(val) OVER (ORDER BY id) AS rs FROM stream_one ORDER BY id;
|
|
471
|
+
→ [{"id":1,"val":42,"rs":42}]
|
|
472
|
+
|
|
473
|
+
-- Empty result via WHERE 1=0: streaming yields zero rows, no errors.
|
|
474
|
+
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM stream_t WHERE 1=0;
|
|
475
|
+
→ []
|
|
476
|
+
|
|
477
|
+
DROP TABLE stream_t;
|
|
478
|
+
DROP TABLE stream_n;
|
|
479
|
+
DROP TABLE stream_one;
|
|
480
|
+
|
|
481
|
+
-- =============================================================================
|
|
482
|
+
-- Shared-subtree row-context regression tests.
|
|
483
|
+
--
|
|
484
|
+
-- Two scalar subqueries against the same CTE share a plan subtree. Without
|
|
485
|
+
-- serialized callback evaluation (see ticket
|
|
486
|
+
-- serialize-emit-callbacks-returning-window), parallel `Promise.all` over
|
|
487
|
+
-- the partition / ORDER BY callbacks raced on the shared inner-scan RowSlot
|
|
488
|
+
-- under real async I/O (LevelDB store), producing wrong/null partition keys.
|
|
489
|
+
-- =============================================================================
|
|
490
|
+
|
|
491
|
+
create table tr (id integer primary key, v integer);
|
|
492
|
+
insert into tr values (1, 10), (2, 20), (3, 30);
|
|
493
|
+
|
|
494
|
+
-- PARTITION BY with two scalar subqueries against the same CTE.
|
|
495
|
+
-- Both keys are constants (same for every row), so all rows land in a single
|
|
496
|
+
-- partition and rn must be 1,2,3.
|
|
497
|
+
with high as (select * from tr where v >= 20)
|
|
498
|
+
select
|
|
499
|
+
row_number() over (
|
|
500
|
+
partition by (select count(*) from high), (select sum(v) from high)
|
|
501
|
+
order by id
|
|
502
|
+
) as rn
|
|
503
|
+
from tr
|
|
504
|
+
order by id;
|
|
505
|
+
→ [{"rn":1},{"rn":2},{"rn":3}]
|
|
506
|
+
|
|
507
|
+
-- ORDER BY with two scalar subqueries against the same CTE.
|
|
508
|
+
-- Both keys are constants, so the entire result is one peer group; the outer
|
|
509
|
+
-- ORDER BY v then yields rn=1,2,3 in v order.
|
|
510
|
+
with high as (select * from tr where v >= 20)
|
|
511
|
+
select
|
|
512
|
+
v,
|
|
513
|
+
row_number() over (
|
|
514
|
+
order by (select count(*) from high), (select sum(v) from high)
|
|
515
|
+
) as rn
|
|
516
|
+
from tr
|
|
517
|
+
order by v;
|
|
518
|
+
→ [{"v":10,"rn":1},{"v":20,"rn":2},{"v":30,"rn":3}]
|
|
519
|
+
|
|
520
|
+
drop table tr;
|
|
521
|
+
|
|
522
|
+
-- =============================================================================
|
|
523
|
+
-- Streaming sliding-frame tests (rule: monotonic-window).
|
|
524
|
+
--
|
|
525
|
+
-- These verify the streaming sliding-frame path under
|
|
526
|
+
-- `ROWS BETWEEN n PRECEDING AND m FOLLOWING` and
|
|
527
|
+
-- `RANGE BETWEEN <num> PRECEDING AND <num> FOLLOWING`. The leaf table's PK
|
|
528
|
+
-- provides the monotonicOn(id) advertisement that the rule keys off.
|
|
529
|
+
-- =============================================================================
|
|
530
|
+
|
|
531
|
+
CREATE TABLE stream_s (id INTEGER PRIMARY KEY, val INTEGER, grp TEXT);
|
|
532
|
+
INSERT INTO stream_s VALUES
|
|
533
|
+
(1, 10, 'A'), (2, 20, 'A'), (3, 30, 'A'),
|
|
534
|
+
(4, 40, 'B'), (5, 50, 'B'), (6, 60, 'B');
|
|
535
|
+
|
|
536
|
+
-- ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING — SUM
|
|
537
|
+
SELECT id, SUM(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS s
|
|
538
|
+
FROM stream_s ORDER BY id;
|
|
539
|
+
→ [{"id":1,"s":30},{"id":2,"s":60},{"id":3,"s":90},{"id":4,"s":120},{"id":5,"s":150},{"id":6,"s":110}]
|
|
540
|
+
|
|
541
|
+
-- ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING — COUNT
|
|
542
|
+
SELECT id, COUNT(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS c
|
|
543
|
+
FROM stream_s ORDER BY id;
|
|
544
|
+
→ [{"id":1,"c":2},{"id":2,"c":3},{"id":3,"c":3},{"id":4,"c":3},{"id":5,"c":3},{"id":6,"c":2}]
|
|
545
|
+
|
|
546
|
+
-- ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING — AVG
|
|
547
|
+
SELECT id, AVG(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS a
|
|
548
|
+
FROM stream_s ORDER BY id;
|
|
549
|
+
→ [{"id":1,"a":15},{"id":2,"a":20},{"id":3,"a":30},{"id":4,"a":40},{"id":5,"a":50},{"id":6,"a":55}]
|
|
550
|
+
|
|
551
|
+
-- ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING — MIN / MAX
|
|
552
|
+
SELECT id,
|
|
553
|
+
MIN(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS mn,
|
|
554
|
+
MAX(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS mx
|
|
555
|
+
FROM stream_s ORDER BY id;
|
|
556
|
+
→ [{"id":1,"mn":10,"mx":20},{"id":2,"mn":10,"mx":30},{"id":3,"mn":20,"mx":40},{"id":4,"mn":30,"mx":50},{"id":5,"mn":40,"mx":60},{"id":6,"mn":50,"mx":60}]
|
|
557
|
+
|
|
558
|
+
-- Asymmetric: ROWS BETWEEN 2 PRECEDING AND 0 FOLLOWING — SUM
|
|
559
|
+
SELECT id, SUM(val) OVER (ORDER BY id ROWS BETWEEN 2 PRECEDING AND 0 FOLLOWING) AS s
|
|
560
|
+
FROM stream_s ORDER BY id;
|
|
561
|
+
→ [{"id":1,"s":10},{"id":2,"s":30},{"id":3,"s":60},{"id":4,"s":90},{"id":5,"s":120},{"id":6,"s":150}]
|
|
562
|
+
|
|
563
|
+
-- One-sided right: ROWS BETWEEN 0 PRECEDING AND 2 FOLLOWING — SUM
|
|
564
|
+
SELECT id, SUM(val) OVER (ORDER BY id ROWS BETWEEN 0 PRECEDING AND 2 FOLLOWING) AS s
|
|
565
|
+
FROM stream_s ORDER BY id;
|
|
566
|
+
→ [{"id":1,"s":60},{"id":2,"s":90},{"id":3,"s":120},{"id":4,"s":150},{"id":5,"s":110},{"id":6,"s":60}]
|
|
567
|
+
|
|
568
|
+
-- Edge clamping: window much larger than partition (5 PRECEDING AND 5 FOLLOWING).
|
|
569
|
+
-- Every row sees the full partition sum (210).
|
|
570
|
+
SELECT id, SUM(val) OVER (ORDER BY id ROWS BETWEEN 5 PRECEDING AND 5 FOLLOWING) AS s
|
|
571
|
+
FROM stream_s ORDER BY id;
|
|
572
|
+
→ [{"id":1,"s":210},{"id":2,"s":210},{"id":3,"s":210},{"id":4,"s":210},{"id":5,"s":210},{"id":6,"s":210}]
|
|
573
|
+
|
|
574
|
+
-- FIRST_VALUE / LAST_VALUE under sliding ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
|
|
575
|
+
SELECT id,
|
|
576
|
+
FIRST_VALUE(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS fv,
|
|
577
|
+
LAST_VALUE(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS lv
|
|
578
|
+
FROM stream_s ORDER BY id;
|
|
579
|
+
→ [{"id":1,"fv":10,"lv":20},{"id":2,"fv":10,"lv":30},{"id":3,"fv":20,"lv":40},{"id":4,"fv":30,"lv":50},{"id":5,"fv":40,"lv":60},{"id":6,"fv":50,"lv":60}]
|
|
580
|
+
|
|
581
|
+
-- Single-row partition with a sliding frame (clamps to the lone row).
|
|
582
|
+
CREATE TABLE stream_s_one (id INTEGER PRIMARY KEY, val INTEGER);
|
|
583
|
+
INSERT INTO stream_s_one VALUES (1, 42);
|
|
584
|
+
SELECT id, SUM(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS s,
|
|
585
|
+
MIN(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS mn
|
|
586
|
+
FROM stream_s_one ORDER BY id;
|
|
587
|
+
→ [{"id":1,"s":42,"mn":42}]
|
|
588
|
+
|
|
589
|
+
-- Sliding SUM with NULL argVals: nulls don't contribute to SUM/COUNT.
|
|
590
|
+
CREATE TABLE stream_s_null (id INTEGER PRIMARY KEY, val INTEGER NULL);
|
|
591
|
+
INSERT INTO stream_s_null VALUES (1, 10), (2, NULL), (3, 30), (4, NULL);
|
|
592
|
+
SELECT id,
|
|
593
|
+
SUM(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS s,
|
|
594
|
+
COUNT(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS c
|
|
595
|
+
FROM stream_s_null ORDER BY id;
|
|
596
|
+
→ [{"id":1,"s":10,"c":1},{"id":2,"s":40,"c":2},{"id":3,"s":30,"c":1},{"id":4,"s":30,"c":1}]
|
|
597
|
+
|
|
598
|
+
-- One-sided frames. `CURRENT ROW` is the offset-zero case of the bound it
|
|
599
|
+
-- replaces, so these run the same streaming sliding-buffer state machine as
|
|
600
|
+
-- the two-sided forms above.
|
|
601
|
+
|
|
602
|
+
-- ROWS BETWEEN 2 PRECEDING AND CURRENT ROW — the trailing moving sum.
|
|
603
|
+
SELECT id, SUM(val) OVER (ORDER BY id ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS s
|
|
604
|
+
FROM stream_s ORDER BY id;
|
|
605
|
+
→ [{"id":1,"s":10},{"id":2,"s":30},{"id":3,"s":60},{"id":4,"s":90},{"id":5,"s":120},{"id":6,"s":150}]
|
|
606
|
+
|
|
607
|
+
-- The start-only spelling `ROWS 2 PRECEDING` means the same frame.
|
|
608
|
+
SELECT id, SUM(val) OVER (ORDER BY id ROWS 2 PRECEDING) AS s
|
|
609
|
+
FROM stream_s ORDER BY id;
|
|
610
|
+
→ [{"id":1,"s":10},{"id":2,"s":30},{"id":3,"s":60},{"id":4,"s":90},{"id":5,"s":120},{"id":6,"s":150}]
|
|
611
|
+
|
|
612
|
+
-- ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING — the leading moving sum.
|
|
613
|
+
SELECT id, SUM(val) OVER (ORDER BY id ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING) AS s
|
|
614
|
+
FROM stream_s ORDER BY id;
|
|
615
|
+
→ [{"id":1,"s":60},{"id":2,"s":90},{"id":3,"s":120},{"id":4,"s":150},{"id":5,"s":110},{"id":6,"s":60}]
|
|
616
|
+
|
|
617
|
+
-- ROWS BETWEEN CURRENT ROW AND CURRENT ROW — the single current row.
|
|
618
|
+
SELECT id, SUM(val) OVER (ORDER BY id ROWS BETWEEN CURRENT ROW AND CURRENT ROW) AS s
|
|
619
|
+
FROM stream_s ORDER BY id;
|
|
620
|
+
→ [{"id":1,"s":10},{"id":2,"s":20},{"id":3,"s":30},{"id":4,"s":40},{"id":5,"s":50},{"id":6,"s":60}]
|
|
621
|
+
|
|
622
|
+
-- Trailing MIN / MAX (the running min-max shape over an ordered table).
|
|
623
|
+
SELECT id,
|
|
624
|
+
MIN(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS mn,
|
|
625
|
+
MAX(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS mx
|
|
626
|
+
FROM stream_s ORDER BY id;
|
|
627
|
+
→ [{"id":1,"mn":10,"mx":10},{"id":2,"mn":10,"mx":20},{"id":3,"mn":20,"mx":30},{"id":4,"mn":30,"mx":40},{"id":5,"mn":40,"mx":50},{"id":6,"mn":50,"mx":60}]
|
|
628
|
+
|
|
629
|
+
-- A one-sided frame and the default (running) frame in one query: distinct
|
|
630
|
+
-- frames land in distinct window nodes and each keeps its own strategy.
|
|
631
|
+
SELECT id,
|
|
632
|
+
SUM(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS trailing,
|
|
633
|
+
SUM(val) OVER (ORDER BY id) AS running
|
|
634
|
+
FROM stream_s ORDER BY id;
|
|
635
|
+
→ [{"id":1,"trailing":10,"running":10},{"id":2,"trailing":30,"running":30},{"id":3,"trailing":50,"running":60},{"id":4,"trailing":70,"running":100},{"id":5,"trailing":90,"running":150},{"id":6,"trailing":110,"running":210}]
|
|
636
|
+
|
|
637
|
+
-- Trailing frame with PARTITION BY: the frame restarts at each partition.
|
|
638
|
+
SELECT id, SUM(val) OVER (PARTITION BY grp ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS s
|
|
639
|
+
FROM stream_s ORDER BY id;
|
|
640
|
+
→ [{"id":1,"s":10},{"id":2,"s":30},{"id":3,"s":50},{"id":4,"s":40},{"id":5,"s":90},{"id":6,"s":110}]
|
|
641
|
+
|
|
642
|
+
-- One-sided frame over NULL argument values: NULLs still don't contribute.
|
|
643
|
+
SELECT id,
|
|
644
|
+
SUM(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS s,
|
|
645
|
+
COUNT(val) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS c
|
|
646
|
+
FROM stream_s_null ORDER BY id;
|
|
647
|
+
→ [{"id":1,"s":10,"c":1},{"id":2,"s":10,"c":1},{"id":3,"s":30,"c":1},{"id":4,"s":30,"c":1}]
|
|
648
|
+
|
|
649
|
+
DROP TABLE stream_s;
|
|
650
|
+
DROP TABLE stream_s_one;
|
|
651
|
+
DROP TABLE stream_s_null;
|
|
652
|
+
|
|
653
|
+
-- RANGE BETWEEN with peer ties.
|
|
654
|
+
-- Numeric ORDER BY column (val) with literal numeric offsets. The PK is id,
|
|
655
|
+
-- so the rule may or may not fire here (depends on whether monotonicOn covers
|
|
656
|
+
-- val); the test confirms correctness on whichever path runs.
|
|
657
|
+
CREATE TABLE range_slide (id INTEGER PRIMARY KEY, val INTEGER);
|
|
658
|
+
INSERT INTO range_slide VALUES
|
|
659
|
+
(1, 10), (2, 15), (3, 15), (4, 20), (5, 25), (6, 40);
|
|
660
|
+
|
|
661
|
+
-- val=10: window=[0,20] → 10,15,15,20 = 60
|
|
662
|
+
-- val=15 (twice): window=[5,25] → 10,15,15,20,25 = 85
|
|
663
|
+
-- val=20: window=[10,30] → 10,15,15,20,25 = 85
|
|
664
|
+
-- val=25: window=[15,35] → 15,15,20,25 = 75
|
|
665
|
+
-- val=40: window=[30,50] → 40
|
|
666
|
+
SELECT val, SUM(val) OVER (ORDER BY val RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING) AS s
|
|
667
|
+
FROM range_slide ORDER BY val, id;
|
|
668
|
+
→ [{"val":10,"s":60},{"val":15,"s":85},{"val":15,"s":85},{"val":20,"s":85},{"val":25,"s":75},{"val":40,"s":40}]
|
|
669
|
+
|
|
670
|
+
-- COUNT under same RANGE frame.
|
|
671
|
+
SELECT val, COUNT(*) OVER (ORDER BY val RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING) AS c
|
|
672
|
+
FROM range_slide ORDER BY val, id;
|
|
673
|
+
→ [{"val":10,"c":4},{"val":15,"c":5},{"val":15,"c":5},{"val":20,"c":5},{"val":25,"c":4},{"val":40,"c":1}]
|
|
674
|
+
|
|
675
|
+
-- MIN / MAX under same RANGE frame.
|
|
676
|
+
SELECT val,
|
|
677
|
+
MIN(val) OVER (ORDER BY val RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING) AS mn,
|
|
678
|
+
MAX(val) OVER (ORDER BY val RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING) AS mx
|
|
679
|
+
FROM range_slide ORDER BY val, id;
|
|
680
|
+
→ [{"val":10,"mn":10,"mx":20},{"val":15,"mn":10,"mx":25},{"val":15,"mn":10,"mx":25},{"val":20,"mn":10,"mx":25},{"val":25,"mn":15,"mx":25},{"val":40,"mn":40,"mx":40}]
|
|
681
|
+
|
|
682
|
+
-- One-sided RANGE frames. In RANGE mode `CURRENT ROW` means the whole peer
|
|
683
|
+
-- group, which is exactly the offset-zero interval [v, v] — so the two 15s
|
|
684
|
+
-- must be treated identically at either edge of the frame.
|
|
685
|
+
|
|
686
|
+
-- val=10: [0,10] → 10
|
|
687
|
+
-- val=15 (twice): [5,15] → 10,15,15 = 40
|
|
688
|
+
-- val=20: [10,20] → 10,15,15,20 = 60
|
|
689
|
+
-- val=25: [15,25] → 15,15,20,25 = 75
|
|
690
|
+
-- val=40: [30,40] → 40
|
|
691
|
+
SELECT val, SUM(val) OVER (ORDER BY val RANGE BETWEEN 10 PRECEDING AND CURRENT ROW) AS s
|
|
692
|
+
FROM range_slide ORDER BY val, id;
|
|
693
|
+
→ [{"val":10,"s":10},{"val":15,"s":40},{"val":15,"s":40},{"val":20,"s":60},{"val":25,"s":75},{"val":40,"s":40}]
|
|
694
|
+
|
|
695
|
+
-- The start-only spelling `RANGE 10 PRECEDING` means the same frame.
|
|
696
|
+
SELECT val, SUM(val) OVER (ORDER BY val RANGE 10 PRECEDING) AS s
|
|
697
|
+
FROM range_slide ORDER BY val, id;
|
|
698
|
+
→ [{"val":10,"s":10},{"val":15,"s":40},{"val":15,"s":40},{"val":20,"s":60},{"val":25,"s":75},{"val":40,"s":40}]
|
|
699
|
+
|
|
700
|
+
-- val=10: [10,20] → 10,15,15,20 = 60
|
|
701
|
+
-- val=15 (twice): [15,25] → 15,15,20,25 = 75
|
|
702
|
+
-- val=20: [20,30] → 20,25 = 45
|
|
703
|
+
-- val=25: [25,35] → 25
|
|
704
|
+
-- val=40: [40,50] → 40
|
|
705
|
+
SELECT val, SUM(val) OVER (ORDER BY val RANGE BETWEEN CURRENT ROW AND 10 FOLLOWING) AS s
|
|
706
|
+
FROM range_slide ORDER BY val, id;
|
|
707
|
+
→ [{"val":10,"s":60},{"val":15,"s":75},{"val":15,"s":75},{"val":20,"s":45},{"val":25,"s":25},{"val":40,"s":40}]
|
|
708
|
+
|
|
709
|
+
-- RANGE BETWEEN CURRENT ROW AND CURRENT ROW is exactly the peer group, so the
|
|
710
|
+
-- two val=15 rows each see a frame of 2 rows and everything else sees 1.
|
|
711
|
+
SELECT val,
|
|
712
|
+
COUNT(*) OVER (ORDER BY val RANGE BETWEEN CURRENT ROW AND CURRENT ROW) AS c,
|
|
713
|
+
SUM(val) OVER (ORDER BY val RANGE BETWEEN CURRENT ROW AND CURRENT ROW) AS s
|
|
714
|
+
FROM range_slide ORDER BY val, id;
|
|
715
|
+
→ [{"val":10,"c":1,"s":10},{"val":15,"c":2,"s":30},{"val":15,"c":2,"s":30},{"val":20,"c":1,"s":20},{"val":25,"c":1,"s":25},{"val":40,"c":1,"s":40}]
|
|
716
|
+
|
|
717
|
+
-- NULL ordering values: a NULL-val row's RANGE window is the contiguous NULL
|
|
718
|
+
-- peer span. Finite-val rows' RANGE windows exclude NULL rows entirely.
|
|
719
|
+
CREATE TABLE range_null (id INTEGER PRIMARY KEY, val INTEGER NULL);
|
|
720
|
+
INSERT INTO range_null VALUES (1, NULL), (2, NULL), (3, 10), (4, 20), (5, 30);
|
|
721
|
+
-- ASC NULLS FIRST default (NULLs at the front of the sort order).
|
|
722
|
+
-- val=NULL (id=1,2): window = both NULL rows. SUM(val)=NULL (no non-null vals).
|
|
723
|
+
-- val=10: window=[5,15] over finite vals → just 10. SUM=10.
|
|
724
|
+
-- val=20: window=[15,25] → 20. SUM=20.
|
|
725
|
+
-- val=30: window=[25,35] → 30. SUM=30.
|
|
726
|
+
SELECT id, val, SUM(val) OVER (ORDER BY val RANGE BETWEEN 5 PRECEDING AND 5 FOLLOWING) AS s
|
|
727
|
+
FROM range_null ORDER BY id;
|
|
728
|
+
→ [{"id":1,"val":null,"s":null},{"id":2,"val":null,"s":null},{"id":3,"val":10,"s":10},{"id":4,"val":20,"s":20},{"id":5,"val":30,"s":30}]
|
|
729
|
+
|
|
730
|
+
-- Same, with an interval that reaches down to zero. NULL is not zero: the two
|
|
731
|
+
-- NULL rows must still stay out of val=10's frame. (Regression — the buffered
|
|
732
|
+
-- frame walk read the ORDER BY value with a bare `Number(...)`, which turns
|
|
733
|
+
-- NULL into 0 and pulled both NULL rows in.)
|
|
734
|
+
-- val=NULL (id=1,2): the NULL peer group → no non-null vals → SUM=NULL.
|
|
735
|
+
-- val=10: [0,10] → 10. val=20: [10,20] → 30. val=30: [20,30] → 50.
|
|
736
|
+
SELECT id, val, SUM(val) OVER (ORDER BY val RANGE BETWEEN 10 PRECEDING AND CURRENT ROW) AS s
|
|
737
|
+
FROM range_null ORDER BY id;
|
|
738
|
+
→ [{"id":1,"val":null,"s":null},{"id":2,"val":null,"s":null},{"id":3,"val":10,"s":10},{"id":4,"val":20,"s":30},{"id":5,"val":30,"s":50}]
|
|
739
|
+
|
|
740
|
+
-- A fractional RANGE offset is not a supported frame offset on either physical
|
|
741
|
+
-- path — it must raise, not quietly depend on which plan the optimizer picks.
|
|
742
|
+
SELECT val, SUM(val) OVER (ORDER BY val RANGE BETWEEN 1.5 PRECEDING AND CURRENT ROW) AS s
|
|
743
|
+
FROM range_null ORDER BY val, id;
|
|
744
|
+
-- error: Invalid window frame offset
|
|
745
|
+
|
|
746
|
+
DROP TABLE range_slide;
|
|
747
|
+
DROP TABLE range_null;
|
|
748
|
+
|
|
749
|
+
-- =============================================================================
|
|
750
|
+
-- Window function nested inside an arithmetic / scalar projection expression.
|
|
751
|
+
--
|
|
752
|
+
-- Regression: a window function wrapped in a binary/scalar expression
|
|
753
|
+
-- (e.g. `1000 - row_number() over (...)`) must keep the surrounding expression
|
|
754
|
+
-- and substitute the window result back in — not collapse to the bare window
|
|
755
|
+
-- column nor drop the projection entirely.
|
|
756
|
+
-- =============================================================================
|
|
757
|
+
|
|
758
|
+
CREATE TABLE nested_w (id INTEGER PRIMARY KEY, val INTEGER);
|
|
759
|
+
INSERT INTO nested_w VALUES (1, 10), (2, 20), (3, 30);
|
|
760
|
+
|
|
761
|
+
-- Literal minus window fn: arithmetic must actually apply (999, 998, 997).
|
|
762
|
+
SELECT id, 1000 - ROW_NUMBER() OVER (ORDER BY id) AS v FROM nested_w ORDER BY id;
|
|
763
|
+
→ [{"id":1,"v":999},{"id":2,"v":998},{"id":3,"v":997}]
|
|
764
|
+
|
|
765
|
+
-- Window fn on the left of the operator.
|
|
766
|
+
SELECT id, ROW_NUMBER() OVER (ORDER BY id) + 5 AS v FROM nested_w ORDER BY id;
|
|
767
|
+
→ [{"id":1,"v":6},{"id":2,"v":7},{"id":3,"v":8}]
|
|
768
|
+
|
|
769
|
+
-- Window fn nested inside a scalar function call.
|
|
770
|
+
SELECT id, ABS(1 - ROW_NUMBER() OVER (ORDER BY id)) AS v FROM nested_w ORDER BY id;
|
|
771
|
+
→ [{"id":1,"v":0},{"id":2,"v":1},{"id":3,"v":2}]
|
|
772
|
+
|
|
773
|
+
-- Two different window functions in one arithmetic expression.
|
|
774
|
+
-- rn (order by id) = 1,2,3 ; rank (order by val desc) = 3,2,1 ; rn-rank = -2,0,2.
|
|
775
|
+
SELECT id, ROW_NUMBER() OVER (ORDER BY id) - RANK() OVER (ORDER BY val DESC) AS v
|
|
776
|
+
FROM nested_w ORDER BY id;
|
|
777
|
+
→ [{"id":1,"v":-2},{"id":2,"v":0},{"id":3,"v":2}]
|
|
778
|
+
|
|
779
|
+
-- Mixed: plain columns + a window-wrapped column + a top-level window column.
|
|
780
|
+
SELECT id, val,
|
|
781
|
+
1000 - ROW_NUMBER() OVER (ORDER BY id) AS adj,
|
|
782
|
+
ROW_NUMBER() OVER (ORDER BY id) AS rn
|
|
783
|
+
FROM nested_w ORDER BY id;
|
|
784
|
+
→ [{"id":1,"val":10,"adj":999,"rn":1},{"id":2,"val":20,"adj":998,"rn":2},{"id":3,"val":30,"adj":997,"rn":3}]
|
|
785
|
+
|
|
786
|
+
DROP TABLE nested_w;
|
|
787
|
+
|
|
788
|
+
-- =============================================================================
|
|
789
|
+
-- Window functions in a GROUPED query.
|
|
790
|
+
--
|
|
791
|
+
-- The window runs over the GROUPED rows — one row per group — so
|
|
792
|
+
-- `row_number() over (order by a) ... group by a` numbers the groups, not the
|
|
793
|
+
-- underlying rows. Regression for
|
|
794
|
+
-- bug-window-function-over-grouped-query-crashes: the grouped select list and
|
|
795
|
+
-- the window select list used to be built by two separate walks, leaving a raw
|
|
796
|
+
-- window-function expression in a projection *below* the node that computes
|
|
797
|
+
-- window values ("No emitter registered for WindowFunctionCall").
|
|
798
|
+
--
|
|
799
|
+
-- No primary key on `wg`, so the functional-dependency GROUP BY reduction cannot
|
|
800
|
+
-- rewrite the grouping keys out from under these.
|
|
801
|
+
-- =============================================================================
|
|
802
|
+
|
|
803
|
+
create table wg (a text, b text);
|
|
804
|
+
insert into wg values ('x','1'),('y','2'),('x','3');
|
|
805
|
+
|
|
806
|
+
-- Bare window function over the groups: two groups, so 1 and 2 (not 1..3).
|
|
807
|
+
select a, row_number() over (order by a) as rn from wg group by a order by a;
|
|
808
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
809
|
+
|
|
810
|
+
-- Alongside a real aggregate.
|
|
811
|
+
select a, count(*) as c, row_number() over (order by a) as rn from wg group by a order by a;
|
|
812
|
+
→ [{"a":"x","c":2,"rn":1},{"a":"y","c":1,"rn":2}]
|
|
813
|
+
|
|
814
|
+
-- Window function wrapped in arithmetic keeps the surrounding expression.
|
|
815
|
+
select a, 1000 - row_number() over (order by a) as adj from wg group by a order by a;
|
|
816
|
+
→ [{"a":"x","adj":999},{"a":"y","adj":998}]
|
|
817
|
+
|
|
818
|
+
-- HAVING filters groups before the window sees them, so rn restarts at 1.
|
|
819
|
+
select a, row_number() over (order by a) as rn from wg group by a having count(*) > 1;
|
|
820
|
+
→ [{"a":"x","rn":1}]
|
|
821
|
+
|
|
822
|
+
-- ORDER BY a window alias.
|
|
823
|
+
select a, row_number() over (order by a) as rn from wg group by a order by rn desc;
|
|
824
|
+
→ [{"a":"y","rn":2},{"a":"x","rn":1}]
|
|
825
|
+
|
|
826
|
+
-- COUNT(*) OVER () counts GROUPS (2), not source rows (3).
|
|
827
|
+
select a, count(*) over () as g from wg group by a order by a;
|
|
828
|
+
→ [{"a":"x","g":2},{"a":"y","g":2}]
|
|
829
|
+
|
|
830
|
+
-- LIMIT over the lot.
|
|
831
|
+
select a, row_number() over (order by a) as rn from wg group by a order by rn limit 1;
|
|
832
|
+
→ [{"a":"x","rn":1}]
|
|
833
|
+
|
|
834
|
+
-- Grouped `select *` plus a window column. `group by a, b` collapses no rows
|
|
835
|
+
-- here, so all three groups survive and the window numbers all three.
|
|
836
|
+
select *, row_number() over (order by a, b) as rn from wg group by a, b order by a, b;
|
|
837
|
+
→ [{"a":"x","b":"1","rn":1},{"a":"x","b":"3","rn":2},{"a":"y","b":"2","rn":3}]
|
|
838
|
+
|
|
839
|
+
-- An aggregate already in the SELECT list may be spelled out again in the window
|
|
840
|
+
-- specification — it reads the computed aggregate column.
|
|
841
|
+
select a, count(*) as c, row_number() over (order by count(*) desc) as rn
|
|
842
|
+
from wg group by a order by rn;
|
|
843
|
+
→ [{"a":"x","c":2,"rn":1},{"a":"y","c":1,"rn":2}]
|
|
844
|
+
|
|
845
|
+
-- A window specification is subject to the same GROUP BY restriction as the rest
|
|
846
|
+
-- of the select list: the grouped rows carry only grouping keys and aggregate
|
|
847
|
+
-- results, so an ungrouped column must fail at plan time (it used to reach the
|
|
848
|
+
-- runtime as a base-table reference and die with an internal row-context error).
|
|
849
|
+
select a, row_number() over (order by b) as rn from wg group by a;
|
|
850
|
+
-- error: Column 'b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
851
|
+
|
|
852
|
+
select a, count(*) over (partition by b) as c from wg group by a;
|
|
853
|
+
-- error: Column 'b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
854
|
+
|
|
855
|
+
-- Same rule for a window function's arguments.
|
|
856
|
+
select a, sum(b) over () as s from wg group by a;
|
|
857
|
+
-- error: Column 'b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
858
|
+
|
|
859
|
+
-- A window function above an UNGROUPED aggregate: the aggregation yields exactly
|
|
860
|
+
-- one implicit-group row, and the window runs over that single row, so
|
|
861
|
+
-- `row_number()` is 1. There is no grouping-key redirect for this shape -- an
|
|
862
|
+
-- ungrouped query has no grouping keys to rewrite onto -- and it needs none.
|
|
863
|
+
-- (Matches PostgreSQL. Previously unreachable: the select list was rejected by
|
|
864
|
+
-- the old ungrouped aggregate/non-aggregate mixing rule before the window phase
|
|
865
|
+
-- ever ran.)
|
|
866
|
+
select count(*) as c, row_number() over (order by count(*)) as rn from wg;
|
|
867
|
+
→ [{"c":3,"rn":1}]
|
|
868
|
+
|
|
869
|
+
-- The coverage rule still applies to that shape, and is what keeps it honest: the
|
|
870
|
+
-- single implicit-group row carries only aggregate results, so a window
|
|
871
|
+
-- specification or argument naming a base-table column is rejected with the same
|
|
872
|
+
-- message a grouped query gets.
|
|
873
|
+
select count(*) as c, row_number() over (order by a) as rn from wg;
|
|
874
|
+
-- error: Column 'a' must appear in the GROUP BY clause or be used in an aggregate function
|
|
875
|
+
|
|
876
|
+
select count(*) as c, sum(b) over () as s from wg;
|
|
877
|
+
-- error: Column 'b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
878
|
+
|
|
879
|
+
-- A window function whose ARGUMENTS are column-free runs over that same single
|
|
880
|
+
-- implicit-group row, so it sees exactly one row regardless of how many rows the
|
|
881
|
+
-- aggregate consumed.
|
|
882
|
+
select count(*) as c, sum(1) over (order by count(*)) as s from wg;
|
|
883
|
+
→ [{"c":3,"s":1}]
|
|
884
|
+
|
|
885
|
+
-- And the aggregate-inside-a-window-argument limitation is unchanged for the
|
|
886
|
+
-- ungrouped shape (an aggregate over the aggregated row, not over the input).
|
|
887
|
+
select count(*) as c, sum(count(*)) over () as s from wg;
|
|
888
|
+
-- error: Aggregate function count not allowed in this context
|
|
889
|
+
|
|
890
|
+
-- An aggregate that appears ONLY in the window specification is a named
|
|
891
|
+
-- limitation, not an internal error: it would have to be collected into the
|
|
892
|
+
-- aggregate node before the window phase runs.
|
|
893
|
+
select a, row_number() over (order by count(*)) as rn from wg group by a;
|
|
894
|
+
-- error: Aggregate function count in a window function's ORDER BY is only supported when the same aggregate also appears in the SELECT list
|
|
895
|
+
|
|
896
|
+
-- Same limitation when the window spec's aggregate differs from the SELECT
|
|
897
|
+
-- list's only in its argument (bug-aggregate-reuse-matching-ignores-arguments):
|
|
898
|
+
-- `sum(a+0)` here is not `sum(b+0)`, so it is not the aggregate already
|
|
899
|
+
-- collected for the SELECT list and must be rejected the same way.
|
|
900
|
+
select a, sum(b+0) as s, row_number() over (order by sum(a+0)) as rn from wg group by a;
|
|
901
|
+
-- error: Aggregate function sum in a window function's ORDER BY is only supported when the same aggregate also appears in the SELECT list
|
|
902
|
+
|
|
903
|
+
-- The match is textual, so a *qualifier* divergence lands in that same
|
|
904
|
+
-- limitation even though both spellings name the same column: `sum(w.b)` in the
|
|
905
|
+
-- select list is not the fingerprint `sum(b)` the window spec asks for. Writing
|
|
906
|
+
-- both the same way, or referencing the select-list alias, both work.
|
|
907
|
+
select a, sum(w.b) as s, row_number() over (order by sum(b)) as rn from wg w group by a;
|
|
908
|
+
-- error: Aggregate function sum in a window function's ORDER BY is only supported when the same aggregate also appears in the SELECT list
|
|
909
|
+
|
|
910
|
+
-- Same limitation for a literal-case divergence
|
|
911
|
+
-- (bug-aggregate-match-ignores-string-literal-case): a quoted string literal's
|
|
912
|
+
-- case is part of an aggregate's identity, so `count(nullif(b,'a'))` here is not
|
|
913
|
+
-- `count(nullif(b,'A'))` and must be rejected the same way — reading the wrong
|
|
914
|
+
-- aggregate silently would be worse than this loud error.
|
|
915
|
+
create table wgc (id integer primary key, g integer, b text);
|
|
916
|
+
insert into wgc values (1, 1, 'A'), (2, 1, 'A'), (3, 1, 'a');
|
|
917
|
+
|
|
918
|
+
select g, count(nullif(b,'A')) as c, row_number() over (order by count(nullif(b,'a'))) as w
|
|
919
|
+
from wgc group by g;
|
|
920
|
+
-- error: Aggregate function count in a window function's ORDER BY is only supported when the same aggregate also appears in the SELECT list
|
|
921
|
+
|
|
922
|
+
drop table wgc;
|
|
923
|
+
|
|
924
|
+
-- Two window functions with different specifications each get their own window
|
|
925
|
+
-- output column; neither collapses onto the other's.
|
|
926
|
+
select a, row_number() over (order by a) as p, count(*) over (partition by a) as q
|
|
927
|
+
from wg group by a order by a;
|
|
928
|
+
→ [{"a":"x","p":1,"q":1},{"a":"y","p":2,"q":1}]
|
|
929
|
+
|
|
930
|
+
-- Two textually identical window functions resolve to their own columns too.
|
|
931
|
+
select a, row_number() over (order by a) as p, row_number() over (order by a) as q
|
|
932
|
+
from wg group by a order by a;
|
|
933
|
+
→ [{"a":"x","p":1,"q":1},{"a":"y","p":2,"q":2}]
|
|
934
|
+
|
|
935
|
+
-- A window specification may name an aggregate by its SELECT-list alias — that
|
|
936
|
+
-- resolves through the aggregate output scope, like ORDER BY does.
|
|
937
|
+
select a, count(*) as c, row_number() over (order by c) as rn from wg group by a order by rn;
|
|
938
|
+
→ [{"a":"y","c":1,"rn":1},{"a":"x","c":2,"rn":2}]
|
|
939
|
+
|
|
940
|
+
-- A grouping key may be named in a window specification by ANY spelling that
|
|
941
|
+
-- denotes it, not only the one the GROUP BY used. The aggregate output scope
|
|
942
|
+
-- registers a qualified name only when the GROUP BY key was itself written
|
|
943
|
+
-- qualified, and registers a non-bare key only under a synthetic `group_N` name,
|
|
944
|
+
-- so the other spellings bind to a base-table column the grouped row does not
|
|
945
|
+
-- carry; the window phase redirects those subtrees onto the aggregate's own group
|
|
946
|
+
-- output column (bug-window-spec-reads-base-table-column — they used to die with
|
|
947
|
+
-- an internal "No row context found for column a" error).
|
|
948
|
+
|
|
949
|
+
-- GROUP BY key written qualified, window spec qualified.
|
|
950
|
+
select wg.a, row_number() over (order by wg.a) as rn from wg group by wg.a order by rn;
|
|
951
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
952
|
+
|
|
953
|
+
-- GROUP BY key written bare, window spec table-qualified.
|
|
954
|
+
select a, row_number() over (order by wg.a) as rn from wg group by a order by rn;
|
|
955
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
956
|
+
|
|
957
|
+
-- Same, alias-qualified through a FROM alias.
|
|
958
|
+
select a, row_number() over (order by w.a) as rn from wg w group by a order by rn;
|
|
959
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
960
|
+
|
|
961
|
+
-- A non-bare grouping key spelled out again in the window specification.
|
|
962
|
+
select a || '!' as k, row_number() over (order by a || '!') as rn from wg group by a || '!' order by rn;
|
|
963
|
+
→ [{"k":"x!","rn":1},{"k":"y!","rn":2}]
|
|
964
|
+
|
|
965
|
+
-- Same, in PARTITION BY: each group is its own partition, so every count is 1.
|
|
966
|
+
select a || '!' as k, count(*) over (partition by a || '!') as c from wg group by a || '!' order by k;
|
|
967
|
+
→ [{"k":"x!","c":1},{"k":"y!","c":1}]
|
|
968
|
+
|
|
969
|
+
-- The grouping key nested inside a larger window-specification expression: the
|
|
970
|
+
-- redirect recurses, so only the `a || '!'` subtree is rewritten.
|
|
971
|
+
select a || '!' as k, row_number() over (order by upper(a || '!')) as rn from wg group by a || '!' order by rn;
|
|
972
|
+
→ [{"k":"x!","rn":1},{"k":"y!","rn":2}]
|
|
973
|
+
|
|
974
|
+
-- A window function ARGUMENT naming the grouping key another way works too.
|
|
975
|
+
select a, max(wg.a) over () as m from wg group by a order by a;
|
|
976
|
+
→ [{"a":"x","m":"y"},{"a":"y","m":"y"}]
|
|
977
|
+
|
|
978
|
+
-- Identifier case is not part of a spelling: the fingerprint that matches a
|
|
979
|
+
-- non-bare grouping key folds identifier case.
|
|
980
|
+
select a || '!' as k, row_number() over (order by A || '!') as rn from wg group by a || '!' order by rn;
|
|
981
|
+
→ [{"k":"x!","rn":1},{"k":"y!","rn":2}]
|
|
982
|
+
|
|
983
|
+
-- A quoted literal's case IS part of it, so `a || 'x'` is not the grouping key
|
|
984
|
+
-- `a || 'X'` and its bare `a` is an ungrouped reference.
|
|
985
|
+
select a || 'X' as k, row_number() over (order by a || 'x') as rn from wg group by a || 'X';
|
|
986
|
+
-- error: Column 'a' must appear in the GROUP BY clause or be used in an aggregate function
|
|
987
|
+
|
|
988
|
+
-- A qualified grouping key nested inside a larger window-specification expression.
|
|
989
|
+
select a, row_number() over (order by upper(wg.a)) as rn from wg group by a order by rn;
|
|
990
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
991
|
+
|
|
992
|
+
-- A wrapper around the grouping key survives the redirect.
|
|
993
|
+
select a, row_number() over (order by a collate nocase) as rn from wg group by a order by rn;
|
|
994
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
995
|
+
|
|
996
|
+
-- A *qualified* ungrouped column is not accidentally redirected onto a group key.
|
|
997
|
+
select a, row_number() over (order by wg.b) as rn from wg group by a;
|
|
998
|
+
-- error: Column 'wg.b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
999
|
+
|
|
1000
|
+
-- Neither is a same-shaped expression over the wrong column.
|
|
1001
|
+
select a || '!' as k, row_number() over (order by b || '!') as rn from wg group by a || '!';
|
|
1002
|
+
-- error: Column 'b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1003
|
+
|
|
1004
|
+
-- A grouping key may ALSO be named by the SELECT-list alias of the column that
|
|
1005
|
+
-- selects it, exactly as an *aggregate* alias can (see above). The alias is
|
|
1006
|
+
-- registered in the aggregate output scope beside the key's own name, so it
|
|
1007
|
+
-- resolves straight to the aggregate's group output column — the redirect above
|
|
1008
|
+
-- never has to see it.
|
|
1009
|
+
select a as k, row_number() over (order by k) as rn from wg group by a order by rn;
|
|
1010
|
+
→ [{"k":"x","rn":1},{"k":"y","rn":2}]
|
|
1011
|
+
|
|
1012
|
+
-- In PARTITION BY as well. Each group is its own partition, so every count is 1.
|
|
1013
|
+
select a as k, count(*) over (partition by k) as c from wg group by a order by k;
|
|
1014
|
+
→ [{"k":"x","c":1},{"k":"y","c":1}]
|
|
1015
|
+
|
|
1016
|
+
-- The alias may be defined AFTER the window function that names it: the scope is
|
|
1017
|
+
-- built from the whole select list before any window specification is.
|
|
1018
|
+
select count(*) over (partition by k) as w, a as k from wg group by a order by k;
|
|
1019
|
+
→ [{"w":1,"k":"x"},{"w":1,"k":"y"}]
|
|
1020
|
+
|
|
1021
|
+
-- The GROUP BY key written qualified, the select-list column bare.
|
|
1022
|
+
select a as k, row_number() over (order by k) as rn from wg group by wg.a order by rn;
|
|
1023
|
+
→ [{"k":"x","rn":1},{"k":"y","rn":2}]
|
|
1024
|
+
|
|
1025
|
+
-- A COMPUTED grouping key's alias resolves the same way — the select-list column is
|
|
1026
|
+
-- matched to its key by expression identity rather than by attribute.
|
|
1027
|
+
select upper(a) as k, row_number() over (order by k) as rn from wg group by upper(a) order by rn;
|
|
1028
|
+
→ [{"k":"X","rn":1},{"k":"Y","rn":2}]
|
|
1029
|
+
|
|
1030
|
+
select a || '!' as k, row_number() over (order by k) as rn from wg group by a || '!' order by rn;
|
|
1031
|
+
→ [{"k":"x!","rn":1},{"k":"y!","rn":2}]
|
|
1032
|
+
|
|
1033
|
+
-- Beside an aggregate alias, both resolving through the same scope.
|
|
1034
|
+
select a as k, count(*) as c, row_number() over (order by k) as rn from wg group by a order by rn;
|
|
1035
|
+
→ [{"k":"x","c":2,"rn":1},{"k":"y","c":1,"rn":2}]
|
|
1036
|
+
|
|
1037
|
+
-- Identifier case is immaterial, in either direction.
|
|
1038
|
+
select a as k, row_number() over (order by K) as rn from wg group by a order by rn;
|
|
1039
|
+
→ [{"k":"x","rn":1},{"k":"y","rn":2}]
|
|
1040
|
+
|
|
1041
|
+
-- The alias reaches inside a SUBQUERY in the specification, where it correlates back
|
|
1042
|
+
-- to the grouping key like any other spelling of it. `wg` has 2 rows with a='x' and 1
|
|
1043
|
+
-- with a='y', so y sorts first.
|
|
1044
|
+
select a as k, row_number() over (order by (select count(*) from wg t where t.a = k)) as rn
|
|
1045
|
+
from wg group by a order by rn;
|
|
1046
|
+
→ [{"k":"y","rn":1},{"k":"x","rn":2}]
|
|
1047
|
+
|
|
1048
|
+
-- DELIBERATE: the alias SHADOWS a same-named base-table column. `b` is a real
|
|
1049
|
+
-- column of `wg`, but inside the specification the alias wins and `b` is the
|
|
1050
|
+
-- grouping key `a`. This is the rule an aggregate alias already had; it is not an
|
|
1051
|
+
-- accident of registration order.
|
|
1052
|
+
select a as b, row_number() over (order by b) as rn from wg group by a order by rn;
|
|
1053
|
+
→ [{"b":"x","rn":1},{"b":"y","rn":2}]
|
|
1054
|
+
|
|
1055
|
+
-- DELIBERATE, the other way: an alias is the LOWEST-precedence name in that scope,
|
|
1056
|
+
-- so it does not outrank a GROUPING KEY of the same name. Here `b` is both the
|
|
1057
|
+
-- alias of the `a` key and a grouping key in its own right; `partition by b`
|
|
1058
|
+
-- partitions on the grouping key `b` (three distinct values → three partitions of
|
|
1059
|
+
-- one row), not on the alias (which would give two partitions of 2 and 1).
|
|
1060
|
+
select a as b, count(*) over (partition by b) as c from wg group by a, b order by b;
|
|
1061
|
+
→ [{"b":"x","c":1},{"b":"x","c":1},{"b":"y","c":1}]
|
|
1062
|
+
|
|
1063
|
+
-- Two aliases naming DIFFERENT grouping keys collide, and nothing in the scope can
|
|
1064
|
+
-- arbitrate, so a bare reference to the shared name is ambiguous.
|
|
1065
|
+
select a as k, b as k, row_number() over (order by k) as rn from wg group by a, b;
|
|
1066
|
+
-- error: ambiguous column name: k
|
|
1067
|
+
|
|
1068
|
+
-- The SELECT list itself is NOT widened: a select-list column still cannot name a
|
|
1069
|
+
-- sibling column's alias. The list is built against the pre-aggregate scope, before
|
|
1070
|
+
-- the aggregate output scope exists.
|
|
1071
|
+
select a as k, k as k2 from wg group by a;
|
|
1072
|
+
-- error: Column not found: k
|
|
1073
|
+
|
|
1074
|
+
-- A quoted alias may contain a dot, which the scope's flat string key cannot tell
|
|
1075
|
+
-- apart from the qualified spelling of a grouping key (`wg.a`). The alias is skipped
|
|
1076
|
+
-- rather than registered, so this query keeps planning exactly as it did before
|
|
1077
|
+
-- aliases were registered at all — registering it raised the internal-sounding
|
|
1078
|
+
-- "Symbol 'wg.a' already exists in the same scope."
|
|
1079
|
+
select wg.a as "wg.a", row_number() over (order by a) as rn from wg group by wg.a order by rn;
|
|
1080
|
+
→ [{"wg.a":"x","rn":1},{"wg.a":"y","rn":2}]
|
|
1081
|
+
|
|
1082
|
+
-- Same skip when the dotted alias names a DIFFERENT key than the qualified spelling
|
|
1083
|
+
-- it collides with: `"wg.b"` in the specification is the grouping key `b`, not the
|
|
1084
|
+
-- alias of `a`. Alias precedence is lowest, qualified names included.
|
|
1085
|
+
select wg.a as "wg.b", row_number() over (order by "wg.b") as rn from wg group by wg.a, wg.b order by rn;
|
|
1086
|
+
→ [{"wg.b":"x","rn":1},{"wg.b":"y","rn":2},{"wg.b":"x","rn":3}]
|
|
1087
|
+
|
|
1088
|
+
-- An alias whose grouping key came from a GROUP BY ORDINAL resolves like any other:
|
|
1089
|
+
-- the ordinal is expanded to the select-list expression before the keys are indexed.
|
|
1090
|
+
select a as k, row_number() over (order by k) as rn from wg group by 1 order by rn;
|
|
1091
|
+
→ [{"k":"x","rn":1},{"k":"y","rn":2}]
|
|
1092
|
+
|
|
1093
|
+
-- The alias survives composition: a grouped, windowed query whose specification names
|
|
1094
|
+
-- one is an ordinary subquery source, and a UNION arm of one.
|
|
1095
|
+
select * from (select a as k, count(*) over (partition by k) as c from wg group by a) z order by k;
|
|
1096
|
+
→ [{"k":"x","c":1},{"k":"y","c":1}]
|
|
1097
|
+
|
|
1098
|
+
select a as k, count(*) over (partition by k) as c from wg group by a
|
|
1099
|
+
union all select a, 1 from wg order by k, c;
|
|
1100
|
+
→ [{"k":"x","c":1},{"k":"x","c":1},{"k":"x","c":1},{"k":"y","c":1},{"k":"y","c":1}]
|
|
1101
|
+
|
|
1102
|
+
-- A grouped, windowed query composes as a subquery source: its window column is
|
|
1103
|
+
-- an ordinary output column to the enclosing query.
|
|
1104
|
+
with q as (select a, row_number() over (order by a) as rn from wg group by a)
|
|
1105
|
+
select * from q where rn > 1;
|
|
1106
|
+
→ [{"a":"y","rn":2}]
|
|
1107
|
+
|
|
1108
|
+
-- A window specification may also contain a SUBQUERY that correlates back to the
|
|
1109
|
+
-- grouping key. The redirect and the coverage check both descend through the
|
|
1110
|
+
-- subquery to reach it, keying off the attribute ids the aggregate's INPUT
|
|
1111
|
+
-- produces so the subquery's own columns are left alone
|
|
1112
|
+
-- (bug-window-spec-subquery-reads-base-table-column — every shape below used to
|
|
1113
|
+
-- die with an internal "No row context found for column a" error).
|
|
1114
|
+
|
|
1115
|
+
-- Scalar subquery in ORDER BY: `max(b)` per group is '3' for a='x' and '2' for
|
|
1116
|
+
-- a='y', so ascending puts y first.
|
|
1117
|
+
select a, row_number() over (order by (select max(t.b) from wg t where t.a = wg.a)) as rn
|
|
1118
|
+
from wg group by a order by rn;
|
|
1119
|
+
→ [{"a":"y","rn":1},{"a":"x","rn":2}]
|
|
1120
|
+
|
|
1121
|
+
-- `exists (…)` correlated on the grouping key: true only for the group that has a
|
|
1122
|
+
-- row with b > '2' (a='x'), so y sorts first.
|
|
1123
|
+
select a, row_number() over (order by (case when exists (select 1 from wg t where t.a = wg.a and t.b > '2') then 1 else 0 end)) as rn
|
|
1124
|
+
from wg group by a order by rn;
|
|
1125
|
+
→ [{"a":"y","rn":1},{"a":"x","rn":2}]
|
|
1126
|
+
|
|
1127
|
+
-- `x in (select …)`, correlated on both sides of the membership test.
|
|
1128
|
+
select a, row_number() over (order by (case when wg.a in (select t.a from wg t where t.a = wg.a and t.b > '2') then 1 else 0 end)) as rn
|
|
1129
|
+
from wg group by a order by rn;
|
|
1130
|
+
→ [{"a":"y","rn":1},{"a":"x","rn":2}]
|
|
1131
|
+
|
|
1132
|
+
-- In a window function's ARGUMENT rather than its specification: the per-group
|
|
1133
|
+
-- values '3' and '2' summed over the whole (unpartitioned) window.
|
|
1134
|
+
select a, sum((select max(t.b) from wg t where t.a = wg.a)) over () as s
|
|
1135
|
+
from wg group by a order by a;
|
|
1136
|
+
→ [{"a":"x","s":5},{"a":"y","s":5}]
|
|
1137
|
+
|
|
1138
|
+
-- In PARTITION BY: the correlated subquery yields a distinct value per group, so
|
|
1139
|
+
-- every partition holds exactly one grouped row.
|
|
1140
|
+
select a, count(*) over (partition by (select max(t.b) from wg t where t.a = wg.a)) as c
|
|
1141
|
+
from wg group by a order by a;
|
|
1142
|
+
→ [{"a":"x","c":1},{"a":"y","c":1}]
|
|
1143
|
+
|
|
1144
|
+
-- The mirror image, and the reason the redirect cannot simply match by name: a
|
|
1145
|
+
-- BARE `a` written inside the subquery names the subquery's OWN `t.a`, not the
|
|
1146
|
+
-- grouping key, so the predicate is the tautology `t.a = t.a` and every group sees
|
|
1147
|
+
-- the same global `max(b)`. One partition of two grouped rows, not two of one.
|
|
1148
|
+
select a, count(*) over (partition by (select max(t.b) from wg t where t.a = a)) as c
|
|
1149
|
+
from wg group by a order by a;
|
|
1150
|
+
→ [{"a":"x","c":2},{"a":"y","c":2}]
|
|
1151
|
+
|
|
1152
|
+
-- A correlated reference to a NON-grouped column is rejected at plan time, with the
|
|
1153
|
+
-- same message a bare ungrouped column in a window specification gets.
|
|
1154
|
+
select a, row_number() over (order by (select max(t.b) from wg t where t.b = wg.b)) as rn
|
|
1155
|
+
from wg group by a;
|
|
1156
|
+
-- error: Column 'wg.b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1157
|
+
|
|
1158
|
+
-- Same rejection when the ungrouped column is only on one side of the correlation.
|
|
1159
|
+
select a, row_number() over (order by (select count(*) from wg t where t.a = wg.b)) as rn
|
|
1160
|
+
from wg group by a;
|
|
1161
|
+
-- error: Column 'wg.b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1162
|
+
|
|
1163
|
+
-- An UNCORRELATED subquery in a window specification reads nothing of this query's
|
|
1164
|
+
-- and is left entirely alone.
|
|
1165
|
+
select a, row_number() over (order by (select max(t.b) from wg t), a) as rn
|
|
1166
|
+
from wg group by a order by rn;
|
|
1167
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
1168
|
+
|
|
1169
|
+
-- A window specification inside a GROUPED SUBQUERY may correlate to the ENCLOSING
|
|
1170
|
+
-- query. `o.a` is not one of the inner query's pre-grouping columns, so the inner
|
|
1171
|
+
-- coverage check has no business rejecting it — it used to, because its only
|
|
1172
|
+
-- vocabulary was "is this an aggregate output column".
|
|
1173
|
+
select o.a,
|
|
1174
|
+
(select count(*) from (select row_number() over (order by i.b, o.a) rn
|
|
1175
|
+
from wg i where i.a = o.a group by i.b) z) c
|
|
1176
|
+
from wg o group by o.a order by o.a;
|
|
1177
|
+
→ [{"a":"x","c":2},{"a":"y","c":1}]
|
|
1178
|
+
|
|
1179
|
+
-- A whole SUBQUERY can be the grouping key, and repeating it verbatim in the window
|
|
1180
|
+
-- specification must resolve to that key's output column. It is matched by identity
|
|
1181
|
+
-- fingerprint at the top level of the specification, where the expression is written
|
|
1182
|
+
-- in this query's own scope — the "does this subtree read only pre-grouping columns"
|
|
1183
|
+
-- test that guards the same rule INSIDE a subquery cannot be applied here, because a
|
|
1184
|
+
-- correlated grouping key legitimately reads the subquery's columns too. Two groups:
|
|
1185
|
+
-- max(b) is '3' for a='x' and '2' for a='y'.
|
|
1186
|
+
select count(*) as n, row_number() over (order by (select max(t.b) from wg t where t.a = wg.a)) as rn
|
|
1187
|
+
from wg group by (select max(t.b) from wg t where t.a = wg.a) order by rn;
|
|
1188
|
+
→ [{"n":1,"rn":1},{"n":2,"rn":2}]
|
|
1189
|
+
|
|
1190
|
+
-- The mirror: an expression grouping key named from INSIDE a subquery in the
|
|
1191
|
+
-- specification. `wgk` has no column `a`, so the bare `a` resolves outward to this
|
|
1192
|
+
-- query's grouping key and IS redirected. Each group matches one `wgk` row, so both
|
|
1193
|
+
-- grouped rows land in the same partition.
|
|
1194
|
+
create table wgk (c text);
|
|
1195
|
+
insert into wgk values ('x!'), ('y!'), ('z!');
|
|
1196
|
+
|
|
1197
|
+
select a || '!' as k, count(*) over (partition by (select count(*) from wgk t where t.c = a || '!')) as c
|
|
1198
|
+
from wg group by a || '!' order by k;
|
|
1199
|
+
→ [{"k":"x!","c":2},{"k":"y!","c":2}]
|
|
1200
|
+
|
|
1201
|
+
drop table wgk;
|
|
1202
|
+
|
|
1203
|
+
-- An aggregate inside a subquery in the window specification belongs to the
|
|
1204
|
+
-- SUBQUERY, so its arguments get no exemption from the coverage check: `wg.b` is a
|
|
1205
|
+
-- correlated reference to an ungrouped column of the grouped query and is rejected at
|
|
1206
|
+
-- plan time. (Only this query's OWN top-level aggregates read pre-grouping columns by
|
|
1207
|
+
-- definition — see the `count(*) over (order by count(*) desc)` cases above.)
|
|
1208
|
+
select a, row_number() over (order by (select max(wg.b) from wg t)) as rn
|
|
1209
|
+
from wg group by a;
|
|
1210
|
+
-- error: Column 'wg.b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1211
|
+
|
|
1212
|
+
select a, row_number() over (order by (select count(wg.b) from wg t where t.a = wg.a)) as rn
|
|
1213
|
+
from wg group by a;
|
|
1214
|
+
-- error: Column 'wg.b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1215
|
+
|
|
1216
|
+
-- The GROUPING key inside such an aggregate is legal, and is redirected onto the
|
|
1217
|
+
-- aggregate's output column: max('x'||b) is 'x3', max('y'||b) is 'y3'.
|
|
1218
|
+
select a, row_number() over (order by (select max(wg.a || t.b) from wg t)) as rn
|
|
1219
|
+
from wg group by a order by rn;
|
|
1220
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
1221
|
+
|
|
1222
|
+
-- A CTE is planned ONCE and shared by every reference to it, so when the grouped
|
|
1223
|
+
-- query and a subquery in its window specification both read the same CTE, the CTE
|
|
1224
|
+
-- body is reachable from both. Its own columns are not this query's pre-grouping
|
|
1225
|
+
-- columns and must not be treated as such — this rejected with "Column 'b' must
|
|
1226
|
+
-- appear in the GROUP BY clause", naming a column written only inside the CTE.
|
|
1227
|
+
with c as (select a, b from wg where b <> '')
|
|
1228
|
+
select a, row_number() over (order by (select count(*) from c z), a) as rn
|
|
1229
|
+
from c group by a order by rn;
|
|
1230
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
1231
|
+
|
|
1232
|
+
-- Same shape, correlated: the redirect still reaches the grouping key through the
|
|
1233
|
+
-- shared CTE. `max(b)` is '3' for a='x' and '2' for a='y', so y sorts first.
|
|
1234
|
+
with c as (select a, b from wg where b <> '')
|
|
1235
|
+
select a, row_number() over (order by (select max(t.b) from c t where t.a = c.a)) as rn
|
|
1236
|
+
from c group by a order by rn;
|
|
1237
|
+
→ [{"a":"y","rn":1},{"a":"x","rn":2}]
|
|
1238
|
+
|
|
1239
|
+
-- And an ungrouped correlated reference is still rejected when the CTE is shared.
|
|
1240
|
+
with c as (select a, b from wg where b <> '')
|
|
1241
|
+
select a, row_number() over (order by (select max(t.b) from c t where t.b = c.b)) as rn
|
|
1242
|
+
from c group by a;
|
|
1243
|
+
-- error: Column 'c.b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1244
|
+
|
|
1245
|
+
-- A RECURSIVE CTE is shared the same way.
|
|
1246
|
+
with recursive r(n) as (select 1 union all select n + 1 from r where n < 3)
|
|
1247
|
+
select n, row_number() over (order by (select count(*) from r z), n) as rn
|
|
1248
|
+
from r group by n order by rn;
|
|
1249
|
+
→ [{"n":1,"rn":1},{"n":2,"rn":2},{"n":3,"rn":3}]
|
|
1250
|
+
|
|
1251
|
+
-- =============================================================================
|
|
1252
|
+
-- The SELECT LIST of a grouped query names a grouping key.
|
|
1253
|
+
--
|
|
1254
|
+
-- Same claim as the window-specification cases above, one clause over: a
|
|
1255
|
+
-- select-list reference to a grouping key must resolve to the AggregateNode's own
|
|
1256
|
+
-- group output column, whatever spelling it used. The aggregate output scope only
|
|
1257
|
+
-- holds a grouping key under the names the query literally wrote, so a qualifier
|
|
1258
|
+
-- the GROUP BY did not use, a FROM-alias qualifier, or a key nested inside a
|
|
1259
|
+
-- bigger expression all fall through to a BASE-TABLE attribute the grouped row
|
|
1260
|
+
-- does not carry. `buildFinalAggregateProjections` runs the same grouping-key
|
|
1261
|
+
-- redirect the window phase does — both through `redirectPostAggregate`
|
|
1262
|
+
-- (bug-qualified-group-key-in-select-list-breaks-window-query).
|
|
1263
|
+
--
|
|
1264
|
+
-- Each windowed case below is paired with its NON-window twin. The twin answers
|
|
1265
|
+
-- correctly today only because `emit/aggregate.ts` publishes a representative
|
|
1266
|
+
-- source row of the group around each yield and the projection sits directly on
|
|
1267
|
+
-- that yield (docs/runtime.md § Corollary: a published source row reaches only the
|
|
1268
|
+
-- adjacent consumer) — the window function is simply the operator that buffers in
|
|
1269
|
+
-- between and exposes the mis-binding. Both spellings must bind the same way, so
|
|
1270
|
+
-- both are pinned.
|
|
1271
|
+
-- =============================================================================
|
|
1272
|
+
|
|
1273
|
+
-- The reported shape: table-qualified select-list key, bare GROUP BY. The output
|
|
1274
|
+
-- column is named `a`, not `wg.a` — the alias comes from the written column name
|
|
1275
|
+
-- and the redirect must not change it.
|
|
1276
|
+
select wg.a, row_number() over (order by a) as rn from wg group by a order by rn;
|
|
1277
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
1278
|
+
|
|
1279
|
+
select wg.a, count(*) as c from wg group by a order by a;
|
|
1280
|
+
→ [{"a":"x","c":2},{"a":"y","c":1}]
|
|
1281
|
+
|
|
1282
|
+
-- Qualified through a FROM ALIAS rather than the table name. Attribute ids are
|
|
1283
|
+
-- minted per relation instance, so `w.a` and `wg.a` and bare `a` all land on the
|
|
1284
|
+
-- same base attribute and the match is qualifier-independent.
|
|
1285
|
+
select w.a, row_number() over (order by a) as rn from wg w group by a order by rn;
|
|
1286
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
1287
|
+
|
|
1288
|
+
select w.a, count(*) as c from wg w group by a order by a;
|
|
1289
|
+
→ [{"a":"x","c":2},{"a":"y","c":1}]
|
|
1290
|
+
|
|
1291
|
+
-- With an explicit alias, which the redirect leaves alone.
|
|
1292
|
+
select wg.a as k, row_number() over (order by a) as rn from wg group by a order by rn;
|
|
1293
|
+
→ [{"k":"x","rn":1},{"k":"y","rn":2}]
|
|
1294
|
+
|
|
1295
|
+
-- Alongside an aggregate, with the window ordered by that aggregate's alias.
|
|
1296
|
+
select wg.a, count(*) as c, row_number() over (order by c) as rn from wg group by a order by rn;
|
|
1297
|
+
→ [{"a":"y","c":1,"rn":1},{"a":"x","c":2,"rn":2}]
|
|
1298
|
+
|
|
1299
|
+
-- Window specification qualified as well, so both redirects run on one query.
|
|
1300
|
+
select wg.a, row_number() over (order by wg.a) as rn from wg group by a order by rn;
|
|
1301
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
1302
|
+
|
|
1303
|
+
-- A qualified grouping key NESTED inside a larger select-list expression: the walk
|
|
1304
|
+
-- recurses, so only the `wg.a` reference is rewritten.
|
|
1305
|
+
select upper(wg.a) as k, row_number() over (order by a) as rn from wg group by a order by rn;
|
|
1306
|
+
→ [{"k":"X","rn":1},{"k":"Y","rn":2}]
|
|
1307
|
+
|
|
1308
|
+
select upper(wg.a) as k from wg group by a order by k;
|
|
1309
|
+
→ [{"k":"X"},{"k":"Y"}]
|
|
1310
|
+
|
|
1311
|
+
-- A COMPUTED grouping key nested inside a larger select-list expression. The
|
|
1312
|
+
-- whole-expression fingerprint fast path cannot see this one (`upper(a || '!')` is
|
|
1313
|
+
-- not the key `a || '!'`); the redirect matches the inner subtree instead.
|
|
1314
|
+
select upper(a || '!') as k, row_number() over (order by a || '!') as rn from wg group by a || '!' order by rn;
|
|
1315
|
+
→ [{"k":"X!","rn":1},{"k":"Y!","rn":2}]
|
|
1316
|
+
|
|
1317
|
+
select upper(a || '!') as k from wg group by a || '!' order by k;
|
|
1318
|
+
→ [{"k":"X!"},{"k":"Y!"}]
|
|
1319
|
+
|
|
1320
|
+
-- A qualified key inside an ordinary binary expression.
|
|
1321
|
+
select wg.a || '?' as k, row_number() over (order by a) as rn from wg group by a order by rn;
|
|
1322
|
+
→ [{"k":"x?","rn":1},{"k":"y?","rn":2}]
|
|
1323
|
+
|
|
1324
|
+
select wg.a || '?' as k from wg group by a order by k;
|
|
1325
|
+
→ [{"k":"x?"},{"k":"y?"}]
|
|
1326
|
+
|
|
1327
|
+
-- NON-WINDOW REGRESSIONS. These all worked before the select-list redirect existed
|
|
1328
|
+
-- and must return exactly the same rows with it in place.
|
|
1329
|
+
|
|
1330
|
+
-- Bare key — already bound to the group column through the aggregate output scope.
|
|
1331
|
+
select a, count(*) as c from wg group by a order by a;
|
|
1332
|
+
→ [{"a":"x","c":2},{"a":"y","c":1}]
|
|
1333
|
+
|
|
1334
|
+
-- Whole-expression fingerprint fast path, which runs BEFORE the redirect and is
|
|
1335
|
+
-- left untouched by it.
|
|
1336
|
+
select a || '!' as k, count(*) as c from wg group by a || '!' order by k;
|
|
1337
|
+
→ [{"k":"x!","c":2},{"k":"y!","c":1}]
|
|
1338
|
+
|
|
1339
|
+
-- The `SELECT *` arm emits group-column references of its own and is untouched.
|
|
1340
|
+
select * from wg group by a, b order by a, b;
|
|
1341
|
+
→ [{"a":"x","b":"1"},{"a":"x","b":"3"},{"a":"y","b":"2"}]
|
|
1342
|
+
|
|
1343
|
+
select *, row_number() over (order by a, b) as rn from wg group by a, b order by rn;
|
|
1344
|
+
→ [{"a":"x","b":"1","rn":1},{"a":"x","b":"3","rn":2},{"a":"y","b":"2","rn":3}]
|
|
1345
|
+
|
|
1346
|
+
-- A GROUP BY ORDINAL is expanded to the select-list expression before the keys are
|
|
1347
|
+
-- indexed, so the redirect sees an ordinary bare key.
|
|
1348
|
+
select a, count(*) as c from wg group by 1 order by a;
|
|
1349
|
+
→ [{"a":"x","c":2},{"a":"y","c":1}]
|
|
1350
|
+
|
|
1351
|
+
select a, count(*) as c, row_number() over (order by a) as rn from wg group by 1 order by rn;
|
|
1352
|
+
→ [{"a":"x","c":2,"rn":1},{"a":"y","c":1,"rn":2}]
|
|
1353
|
+
|
|
1354
|
+
-- TWO projections onto the SAME grouping key. Both now carry the same input
|
|
1355
|
+
-- attribute id; the duplicate output name is disambiguated as `a:1`, exactly as a
|
|
1356
|
+
-- bare `select a, a` already was.
|
|
1357
|
+
select wg.a, a from wg group by a order by 1;
|
|
1358
|
+
→ [{"a":"x","a:1":"x"},{"a":"y","a:1":"y"}]
|
|
1359
|
+
|
|
1360
|
+
select wg.a, a, row_number() over (order by a) as rn from wg group by a order by rn;
|
|
1361
|
+
→ [{"a":"x","a:1":"x","rn":1},{"a":"y","a:1":"y","rn":2}]
|
|
1362
|
+
|
|
1363
|
+
-- HAVING resolves through its own hybrid scope; an aggregate reference resolves to
|
|
1364
|
+
-- the AggregateNode's output column and needs no redirect.
|
|
1365
|
+
select wg.a, count(*) as c, row_number() over (order by a) as rn from wg group by a having count(*) > 1;
|
|
1366
|
+
→ [{"a":"x","c":2,"rn":1}]
|
|
1367
|
+
|
|
1368
|
+
-- HAVING may itself name the grouping key under a spelling its scope does not hold.
|
|
1369
|
+
-- Such a reference used to bind to the base-table attribute and read correctly only
|
|
1370
|
+
-- because the HAVING filter sits directly on the AggregateNode's yield, where the
|
|
1371
|
+
-- representative source row is still published. It now goes through the same
|
|
1372
|
+
-- grouping-key redirect as every other post-aggregate clause and reads the
|
|
1373
|
+
-- aggregate's own output column, so the predicate stays correct wherever the filter
|
|
1374
|
+
-- ends up relative to a buffering operator. Pinned windowed and unwindowed.
|
|
1375
|
+
select wg.a, count(*) as c from wg group by a having wg.a = 'x';
|
|
1376
|
+
→ [{"a":"x","c":2}]
|
|
1377
|
+
|
|
1378
|
+
select wg.a, count(*) as c, row_number() over (order by a) as rn from wg group by a having wg.a = 'x';
|
|
1379
|
+
→ [{"a":"x","c":2,"rn":1}]
|
|
1380
|
+
|
|
1381
|
+
select wg.a, count(*) as c from wg group by a having upper(wg.a) = 'X';
|
|
1382
|
+
→ [{"a":"x","c":2}]
|
|
1383
|
+
|
|
1384
|
+
select wg.a, count(*) as c, row_number() over (order by a) as rn from wg group by a having upper(wg.a) = 'X';
|
|
1385
|
+
→ [{"a":"x","c":2,"rn":1}]
|
|
1386
|
+
|
|
1387
|
+
-- A COMPUTED grouping key written out again in HAVING — matched by the whole-subtree
|
|
1388
|
+
-- fingerprint, redirected onto the aggregate's group output column.
|
|
1389
|
+
select a || '!' as k, count(*) as c from wg group by a || '!' having a || '!' = 'x!';
|
|
1390
|
+
→ [{"k":"x!","c":2}]
|
|
1391
|
+
|
|
1392
|
+
select a || '!' as k, count(*) as c, row_number() over (order by a || '!') as rn from wg group by a || '!' having a || '!' = 'x!';
|
|
1393
|
+
→ [{"k":"x!","c":2,"rn":1}]
|
|
1394
|
+
|
|
1395
|
+
-- A genuinely ungrouped HAVING column keeps its dedicated rejection, windowed or not.
|
|
1396
|
+
select wg.a, count(*) as c from wg group by a having b = '1';
|
|
1397
|
+
-- error: HAVING references non-grouped column 'b'
|
|
1398
|
+
|
|
1399
|
+
select wg.a, count(*) as c, row_number() over (order by a) as rn from wg group by a having b = '1';
|
|
1400
|
+
-- error: HAVING references non-grouped column 'b'
|
|
1401
|
+
|
|
1402
|
+
-- =============================================================================
|
|
1403
|
+
-- The ORDER BY of a grouped query names a grouping key.
|
|
1404
|
+
--
|
|
1405
|
+
-- The same claim as the SELECT-list cases above, one clause over. A sort key spelled
|
|
1406
|
+
-- in a way the projection output scope does not publish — a qualifier the GROUP BY did
|
|
1407
|
+
-- not use, a FROM-alias qualifier, the key nested inside a bigger expression, a
|
|
1408
|
+
-- computed key written out again — falls through to a BASE-TABLE attribute the grouped
|
|
1409
|
+
-- row does not carry. `applyOrderBy` runs the same grouping-key redirect through
|
|
1410
|
+
-- `redirectPostAggregate`, gated on "this key actually references a pre-grouping
|
|
1411
|
+
-- attribute" (bug-order-by-grouping-key-spelling-breaks-window-query).
|
|
1412
|
+
--
|
|
1413
|
+
-- Each windowed case is paired with its NON-window twin. The twin answered correctly
|
|
1414
|
+
-- before this fix only because its sort consumed the aggregate's yield through
|
|
1415
|
+
-- streaming operators, and `emit/aggregate.ts` publishes a representative source row of
|
|
1416
|
+
-- the group around each yield. A WindowNode drains its whole source before yielding, so
|
|
1417
|
+
-- by the time a sort above it runs every representative row is gone — which is what
|
|
1418
|
+
-- turned the mis-binding into "No row context found for column a".
|
|
1419
|
+
-- =============================================================================
|
|
1420
|
+
|
|
1421
|
+
-- The reported shape: table-qualified sort key, bare GROUP BY, both directions.
|
|
1422
|
+
select a, row_number() over (order by a) as rn from wg group by a order by wg.a;
|
|
1423
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
1424
|
+
|
|
1425
|
+
select a, count(*) as c from wg group by a order by wg.a;
|
|
1426
|
+
→ [{"a":"x","c":2},{"a":"y","c":1}]
|
|
1427
|
+
|
|
1428
|
+
select a, row_number() over (order by a) as rn from wg group by a order by wg.a desc;
|
|
1429
|
+
→ [{"a":"y","rn":2},{"a":"x","rn":1}]
|
|
1430
|
+
|
|
1431
|
+
select a, count(*) as c from wg group by a order by wg.a desc;
|
|
1432
|
+
→ [{"a":"y","c":1},{"a":"x","c":2}]
|
|
1433
|
+
|
|
1434
|
+
-- Qualified through a FROM ALIAS rather than the table name — same base attribute, so
|
|
1435
|
+
-- the match is qualifier-independent.
|
|
1436
|
+
select w.a, row_number() over (order by a) as rn from wg w group by a order by w.a desc;
|
|
1437
|
+
→ [{"a":"y","rn":2},{"a":"x","rn":1}]
|
|
1438
|
+
|
|
1439
|
+
select w.a, count(*) as c from wg w group by a order by w.a desc;
|
|
1440
|
+
→ [{"a":"y","c":1},{"a":"x","c":2}]
|
|
1441
|
+
|
|
1442
|
+
-- The grouping key NESTED under a scalar function: the walk recurses, so only the
|
|
1443
|
+
-- `wg.a` reference is rewritten and the sort still evaluates `upper()` above it.
|
|
1444
|
+
select a, row_number() over (order by a) as rn from wg group by a order by upper(wg.a);
|
|
1445
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
1446
|
+
|
|
1447
|
+
select a, count(*) as c from wg group by a order by upper(wg.a);
|
|
1448
|
+
→ [{"a":"x","c":2},{"a":"y","c":1}]
|
|
1449
|
+
|
|
1450
|
+
select a, row_number() over (order by a) as rn from wg group by a order by upper(wg.a) desc;
|
|
1451
|
+
→ [{"a":"y","rn":2},{"a":"x","rn":1}]
|
|
1452
|
+
|
|
1453
|
+
select a, count(*) as c from wg group by a order by upper(wg.a) desc;
|
|
1454
|
+
→ [{"a":"y","c":1},{"a":"x","c":2}]
|
|
1455
|
+
|
|
1456
|
+
-- The SELECT list and the ORDER BY both spell the key the same wrong way, so the
|
|
1457
|
+
-- select-list redirect and the ORDER BY redirect run on one query.
|
|
1458
|
+
select upper(wg.a) as k, row_number() over (order by a) as rn from wg group by a order by upper(wg.a) desc;
|
|
1459
|
+
→ [{"k":"Y","rn":2},{"k":"X","rn":1}]
|
|
1460
|
+
|
|
1461
|
+
select upper(wg.a) as k, count(*) as c from wg group by a order by upper(wg.a) desc;
|
|
1462
|
+
→ [{"k":"Y","c":1},{"k":"X","c":2}]
|
|
1463
|
+
|
|
1464
|
+
-- A COMPUTED grouping key repeated verbatim in ORDER BY — matched by the whole-subtree
|
|
1465
|
+
-- fingerprint rather than by attribute id.
|
|
1466
|
+
select a || '!' as k, row_number() over (order by a || '!') as rn from wg group by a || '!' order by a || '!';
|
|
1467
|
+
→ [{"k":"x!","rn":1},{"k":"y!","rn":2}]
|
|
1468
|
+
|
|
1469
|
+
select a || '!' as k, count(*) as c from wg group by a || '!' order by a || '!';
|
|
1470
|
+
→ [{"k":"x!","c":2},{"k":"y!","c":1}]
|
|
1471
|
+
|
|
1472
|
+
-- ...and the same computed key nested inside a bigger ORDER BY expression, which the
|
|
1473
|
+
-- whole-expression match cannot see (`upper(a || '!')` is not the key `a || '!'`).
|
|
1474
|
+
select a || '!' as k, row_number() over (order by a || '!') as rn from wg group by a || '!' order by upper(a || '!') desc;
|
|
1475
|
+
→ [{"k":"y!","rn":2},{"k":"x!","rn":1}]
|
|
1476
|
+
|
|
1477
|
+
select a || '!' as k, count(*) as c from wg group by a || '!' order by upper(a || '!') desc;
|
|
1478
|
+
→ [{"k":"y!","c":1},{"k":"x!","c":2}]
|
|
1479
|
+
|
|
1480
|
+
-- The select list ALIASES the key, so the alias is what the projection scope publishes
|
|
1481
|
+
-- and the qualified spelling still has to be redirected.
|
|
1482
|
+
select a as k, row_number() over (order by a) as rn from wg group by a order by wg.a desc;
|
|
1483
|
+
→ [{"k":"y","rn":2},{"k":"x","rn":1}]
|
|
1484
|
+
|
|
1485
|
+
select a as k, count(*) as c from wg group by a order by wg.a desc;
|
|
1486
|
+
→ [{"k":"y","c":1},{"k":"x","c":2}]
|
|
1487
|
+
|
|
1488
|
+
-- Alongside an aggregate in the select list.
|
|
1489
|
+
select wg.a, count(*) as c, row_number() over (order by a) as rn from wg group by a order by wg.a desc;
|
|
1490
|
+
→ [{"a":"y","c":1,"rn":2},{"a":"x","c":2,"rn":1}]
|
|
1491
|
+
|
|
1492
|
+
select wg.a, count(*) as c from wg group by a order by wg.a desc;
|
|
1493
|
+
→ [{"a":"y","c":1},{"a":"x","c":2}]
|
|
1494
|
+
|
|
1495
|
+
-- MIXED sort: a window output column (which resolves through the window output scope
|
|
1496
|
+
-- and gets no redirect) and a redirected grouping key in the same ORDER BY. The
|
|
1497
|
+
-- non-window twin does the same with an aggregate alias.
|
|
1498
|
+
select a, row_number() over (order by a) as rn from wg group by a order by rn desc, wg.a;
|
|
1499
|
+
→ [{"a":"y","rn":2},{"a":"x","rn":1}]
|
|
1500
|
+
|
|
1501
|
+
select a, count(*) as c from wg group by a order by c desc, wg.a;
|
|
1502
|
+
→ [{"a":"x","c":2},{"a":"y","c":1}]
|
|
1503
|
+
|
|
1504
|
+
-- A CORRELATED subquery as the sort key, naming the grouping key at depth. Redirected
|
|
1505
|
+
-- by the base-attribute rule inside the subquery, so `x` (2 rows) sorts after `y` (1).
|
|
1506
|
+
select a, row_number() over (order by a) as rn from wg group by a order by (select count(*) from wg t where t.a = wg.a);
|
|
1507
|
+
→ [{"a":"y","rn":2},{"a":"x","rn":1}]
|
|
1508
|
+
|
|
1509
|
+
select a, count(*) as c from wg group by a order by (select count(*) from wg t where t.a = wg.a);
|
|
1510
|
+
→ [{"a":"y","c":1},{"a":"x","c":2}]
|
|
1511
|
+
|
|
1512
|
+
-- TWO grouping keys, sorting on the qualified spelling of the second one.
|
|
1513
|
+
select a, b, row_number() over (order by a) as rn from wg group by a, b order by wg.b desc;
|
|
1514
|
+
→ [{"a":"x","b":"3","rn":2},{"a":"y","b":"2","rn":3},{"a":"x","b":"1","rn":1}]
|
|
1515
|
+
|
|
1516
|
+
select a, b, count(*) as c from wg group by a, b order by wg.b desc;
|
|
1517
|
+
→ [{"a":"x","b":"3","c":1},{"a":"y","b":"2","c":1},{"a":"x","b":"1","c":1}]
|
|
1518
|
+
|
|
1519
|
+
-- HAVING and a redirected ORDER BY on the same query.
|
|
1520
|
+
select wg.a, count(*) as c, row_number() over (order by a) as rn from wg group by a having count(*) > 1 order by wg.a desc;
|
|
1521
|
+
→ [{"a":"x","c":2,"rn":1}]
|
|
1522
|
+
|
|
1523
|
+
select wg.a, count(*) as c from wg group by a having count(*) > 1 order by wg.a desc;
|
|
1524
|
+
→ [{"a":"x","c":2}]
|
|
1525
|
+
|
|
1526
|
+
-- REGRESSION PINS. The spellings the projection output scope already publishes — the
|
|
1527
|
+
-- bare key, an output alias, a window output column, an ordinal — resolve there and
|
|
1528
|
+
-- must keep resolving there. The redirect's gate is what protects them: an ungated
|
|
1529
|
+
-- fingerprint match would rewrite a correctly-bound `order by a` onto the
|
|
1530
|
+
-- AggregateNode's attribute and break a query that worked before this fix.
|
|
1531
|
+
select wg.a, row_number() over (order by a) as rn from wg group by a order by a;
|
|
1532
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
1533
|
+
|
|
1534
|
+
select wg.a as k, row_number() over (order by a) as rn from wg group by a order by k;
|
|
1535
|
+
→ [{"k":"x","rn":1},{"k":"y","rn":2}]
|
|
1536
|
+
|
|
1537
|
+
select a, row_number() over (order by a) as rn from wg group by a order by rn;
|
|
1538
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
1539
|
+
|
|
1540
|
+
select wg.a, row_number() over (order by a) as rn from wg group by a order by 1;
|
|
1541
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
1542
|
+
|
|
1543
|
+
select a, count(*) as c from wg group by a order by 1;
|
|
1544
|
+
→ [{"a":"x","c":2},{"a":"y","c":1}]
|
|
1545
|
+
|
|
1546
|
+
-- An ORDER BY naming a genuinely UNGROUPED column is now REJECTED at plan time,
|
|
1547
|
+
-- windowed and not. It used to be accepted and sorted by an arbitrary representative
|
|
1548
|
+
-- row's `b` — a wrong-result bug, and inconsistent with the SELECT list and HAVING,
|
|
1549
|
+
-- which already reject ungrouped columns. The finished-plan boundary check
|
|
1550
|
+
-- (`assertGroupedPlanCoverage`, see the NOTE at its call in select.ts) is what
|
|
1551
|
+
-- rejects it, with the same user-facing message the SELECT list uses.
|
|
1552
|
+
select a, row_number() over (order by a) as rn from wg group by a order by b;
|
|
1553
|
+
-- error: Column 'b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1554
|
+
|
|
1555
|
+
select a, count(*) as c from wg group by a order by b;
|
|
1556
|
+
-- error: Column 'b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1557
|
+
|
|
1558
|
+
-- A COMPUTED grouping key is matched by the whole expression's TEXT, so a qualifier
|
|
1559
|
+
-- the GROUP BY did not use makes it a different expression — and what is left is a
|
|
1560
|
+
-- bare reference to the ungrouped `a`. (A bare-column key is matched by attribute id
|
|
1561
|
+
-- instead, which is why `group by a order by wg.a` further down works under any
|
|
1562
|
+
-- qualifier.) The SELECT list has always rejected this spelling; ORDER BY used to
|
|
1563
|
+
-- accept it and sort by an arbitrary representative row, so the pin below is the
|
|
1564
|
+
-- ORDER BY half becoming consistent with the select-list half above it. Tracked as
|
|
1565
|
+
-- backlog/debt-group-key-match-by-attribute-identity.
|
|
1566
|
+
select upper(a) as k, count(*) as c from wg group by upper(a) order by upper(wg.a);
|
|
1567
|
+
-- error: Column 'wg.a' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1568
|
+
|
|
1569
|
+
select upper(wg.a) as k, count(*) as c from wg group by upper(a);
|
|
1570
|
+
-- error: Column 'wg.a' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1571
|
+
|
|
1572
|
+
-- Control: one spelling on both sides matches, windowed and not.
|
|
1573
|
+
select upper(a) as k, count(*) as c from wg group by upper(a) order by upper(a);
|
|
1574
|
+
→ [{"k":"X","c":2},{"k":"Y","c":1}]
|
|
1575
|
+
|
|
1576
|
+
select upper(a) as k, row_number() over (order by upper(a)) as rn from wg group by upper(a);
|
|
1577
|
+
→ [{"k":"X","rn":1},{"k":"Y","rn":2}]
|
|
1578
|
+
|
|
1579
|
+
-- The `preWindowSort` placement (an ORDER BY column absent from the select list gets
|
|
1580
|
+
-- its sort BELOW the window projection) with a QUALIFIED spelling of a grouping key:
|
|
1581
|
+
-- that sort still sits ABOVE the AggregateNode, so its key is redirected onto the
|
|
1582
|
+
-- aggregate's own output column like every other post-aggregate sort key. (The
|
|
1583
|
+
-- window's own ordering wins on output — pre-existing preWindowSort semantics; what
|
|
1584
|
+
-- these pin is that the qualified key plans and evaluates instead of dying.)
|
|
1585
|
+
select a, row_number() over (order by a) as rn from wg group by a, b order by wg.b;
|
|
1586
|
+
→ [{"a":"x","rn":1},{"a":"x","rn":2},{"a":"y","rn":3}]
|
|
1587
|
+
|
|
1588
|
+
select a, row_number() over (order by a) as rn from wg group by a, b order by wg.b desc;
|
|
1589
|
+
→ [{"a":"x","rn":1},{"a":"x","rn":2},{"a":"y","rn":3}]
|
|
1590
|
+
|
|
1591
|
+
-- The EARLY ORDER BY placement (taken when ORDER BY introduces an aggregate the
|
|
1592
|
+
-- select list does not have) alongside a qualified grouping-key spelling: both keys
|
|
1593
|
+
-- of one sort, one resolved through the aggregates context, one redirected.
|
|
1594
|
+
select a from wg group by a order by count(*) desc, wg.a;
|
|
1595
|
+
→ [{"a":"x"},{"a":"y"}]
|
|
1596
|
+
|
|
1597
|
+
select a from wg group by a order by count(*), wg.a;
|
|
1598
|
+
→ [{"a":"y"},{"a":"x"}]
|
|
1599
|
+
|
|
1600
|
+
-- The grouping key is NOT in the select list, so no projection output column carries it
|
|
1601
|
+
-- and the redirect is the only thing that can bind the sort key. A non-column ORDER BY
|
|
1602
|
+
-- expression, so `preWindowSort` does not divert it either — this reaches the redirect
|
|
1603
|
+
-- and reads the AggregateNode's group column through the WindowNode's passthrough.
|
|
1604
|
+
select count(*) as c, row_number() over (order by count(*)) as rn from wg group by a order by upper(wg.a) desc;
|
|
1605
|
+
→ [{"c":1,"rn":1},{"c":2,"rn":2}]
|
|
1606
|
+
|
|
1607
|
+
select count(*) as c from wg group by a order by upper(wg.a) desc;
|
|
1608
|
+
→ [{"c":1},{"c":2}]
|
|
1609
|
+
|
|
1610
|
+
-- DISTINCT sits between the projection and the sort, so the redirected key is read
|
|
1611
|
+
-- through one more operator than the cases above.
|
|
1612
|
+
select distinct a, row_number() over (order by a) as rn from wg group by a order by wg.a desc;
|
|
1613
|
+
→ [{"a":"y","rn":2},{"a":"x","rn":1}]
|
|
1614
|
+
|
|
1615
|
+
select distinct a, count(*) as c from wg group by a order by wg.a desc;
|
|
1616
|
+
→ [{"a":"y","c":1},{"a":"x","c":2}]
|
|
1617
|
+
|
|
1618
|
+
-- =============================================================================
|
|
1619
|
+
-- SELECT LIST of a grouped query, continued.
|
|
1620
|
+
-- =============================================================================
|
|
1621
|
+
|
|
1622
|
+
-- An UNGROUPED select-list column is still rejected at plan time, with the same
|
|
1623
|
+
-- message and the same wording, window function or not. `validateAggregateProjections`
|
|
1624
|
+
-- runs on the pre-aggregate projections and never sees the redirect.
|
|
1625
|
+
select wg.b, row_number() over (order by a) as rn from wg group by a;
|
|
1626
|
+
-- error: Column 'wg.b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1627
|
+
|
|
1628
|
+
select wg.b from wg group by a;
|
|
1629
|
+
-- error: Column 'wg.b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1630
|
+
|
|
1631
|
+
-- A SUBQUERY in the select list whose text spells the grouping key but resolves to
|
|
1632
|
+
-- its OWN column keeps the inner meaning: inside a subquery the fingerprint rule is
|
|
1633
|
+
-- guarded by "does this subtree read only pre-grouping columns of THIS query", and
|
|
1634
|
+
-- `t.a` is the subquery's. So the predicate is the tautology `t.a = t.a` and every
|
|
1635
|
+
-- group counts all 3 rows.
|
|
1636
|
+
select a, (select count(*) from wg t where t.a = a) as c from wg group by a order by a;
|
|
1637
|
+
→ [{"a":"x","c":3},{"a":"y","c":3}]
|
|
1638
|
+
|
|
1639
|
+
select a, (select count(*) from wg t where t.a = a) as c, row_number() over (order by a) as rn from wg group by a order by a;
|
|
1640
|
+
→ [{"a":"x","c":3,"rn":1},{"a":"y","c":3,"rn":2}]
|
|
1641
|
+
|
|
1642
|
+
-- A genuinely CORRELATED select-list subquery naming the grouping key is redirected
|
|
1643
|
+
-- through the subquery, by attribute id, at any depth — it used to die with an
|
|
1644
|
+
-- internal "No row context found for column a".
|
|
1645
|
+
select a, (select count(*) from wg t where t.a = wg.a) as c from wg group by a order by a;
|
|
1646
|
+
→ [{"a":"x","c":2},{"a":"y","c":1}]
|
|
1647
|
+
|
|
1648
|
+
select a, (select t.b from wg t where t.a = wg.a and t.b = '1') as c, row_number() over (order by a) as rn
|
|
1649
|
+
from wg group by a order by rn;
|
|
1650
|
+
→ [{"a":"x","c":"1","rn":1},{"a":"y","c":null,"rn":2}]
|
|
1651
|
+
|
|
1652
|
+
-- A correlated subquery reading a genuinely UNGROUPED column of this query is
|
|
1653
|
+
-- rejected at plan time by the finished-plan boundary check — it used to plan and
|
|
1654
|
+
-- then read an arbitrary representative row (or die at run time with an internal
|
|
1655
|
+
-- "No row context found" error once a WindowNode sat in between). The subquery's own
|
|
1656
|
+
-- `t.b` is a different attribute instance and stays legal; only the correlated
|
|
1657
|
+
-- `wg.b` is flagged.
|
|
1658
|
+
select a, (select count(*) from wg t where t.b = wg.b) as c from wg group by a;
|
|
1659
|
+
-- error: Column 'wg.b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1660
|
+
|
|
1661
|
+
select a, (select count(*) from wg t where t.b = wg.b) as c, row_number() over (order by a) as rn from wg group by a;
|
|
1662
|
+
-- error: Column 'wg.b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1663
|
+
|
|
1664
|
+
select a, count(*) as c from wg group by a order by (select count(*) from wg t where t.b = wg.b);
|
|
1665
|
+
-- error: Column 'wg.b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1666
|
+
|
|
1667
|
+
-- The check walks the finished PLAN, not any one clause, so the other correlated
|
|
1668
|
+
-- shapes reject identically: an EXISTS and an IN, each correlated on the ungrouped
|
|
1669
|
+
-- `wg.b`. (HAVING's own bare-column rejection never sees these — its check does not
|
|
1670
|
+
-- descend into a subquery.)
|
|
1671
|
+
select a, count(*) as c from wg group by a having exists (select 1 from wg t where t.a = wg.b);
|
|
1672
|
+
-- error: Column 'wg.b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1673
|
+
|
|
1674
|
+
select a from wg group by a having a in (select t.a from wg t where t.b = wg.b);
|
|
1675
|
+
-- error: Column 'wg.b' must appear in the GROUP BY clause or be used in an aggregate function
|
|
1676
|
+
|
|
1677
|
+
-- Control: the same two shapes correlated on the GROUPING KEY are redirected and run.
|
|
1678
|
+
select a, count(*) as c from wg group by a having exists (select 1 from wg t where t.a = wg.a) order by wg.a;
|
|
1679
|
+
→ [{"a":"x","c":2},{"a":"y","c":1}]
|
|
1680
|
+
|
|
1681
|
+
select a from wg group by a having a in (select t.a from wg t where t.a = wg.a) order by wg.a;
|
|
1682
|
+
→ [{"a":"x"},{"a":"y"}]
|
|
1683
|
+
|
|
1684
|
+
-- =============================================================================
|
|
1685
|
+
-- A WINDOW RESULT COLUMN IS ADDRESSED BY ATTRIBUTE IDENTITY, NOT POSITION.
|
|
1686
|
+
-- Regression for bug-window-column-read-by-position-hits-wrong-row: the window
|
|
1687
|
+
-- phase used to hand the projection a POSITIONAL read of the WindowNode's row,
|
|
1688
|
+
-- which silently read whatever row context was newest. Whenever decorrelation
|
|
1689
|
+
-- placed a join between the WindowNode and its projection, the window column
|
|
1690
|
+
-- came back as a verbatim copy of the subquery's value. Non-numeric text values
|
|
1691
|
+
-- ('p','q','r') on purpose: numeric-looking text picks up numeric affinity on
|
|
1692
|
+
-- the way through and muddies WHICH column is wrong.
|
|
1693
|
+
-- =============================================================================
|
|
1694
|
+
create table wsq (a text, b text);
|
|
1695
|
+
insert into wsq values ('x','p'),('y','q'),('x','r');
|
|
1696
|
+
|
|
1697
|
+
-- Correlated scalar-aggregate subquery (routes through rule-scalar-agg-decorrelation,
|
|
1698
|
+
-- which puts a grouped LEFT JOIN above the WindowNode) alongside each window shape.
|
|
1699
|
+
-- `n` used to come back as a copy of `c`.
|
|
1700
|
+
select k, (select min(t.b) from wsq t where t.a = k) as c, count(*) over () as n from (select a as k from wsq) group by k;
|
|
1701
|
+
→ [{"k":"x","c":"p","n":2},{"k":"y","c":"q","n":2}]
|
|
1702
|
+
|
|
1703
|
+
select k, (select min(t.b) from wsq t where t.a = k) as c, row_number() over (order by k) as rn from (select a as k from wsq) group by k;
|
|
1704
|
+
→ [{"k":"x","c":"p","rn":1},{"k":"y","c":"q","rn":2}]
|
|
1705
|
+
|
|
1706
|
+
-- The desc spelling additionally needs the window's honest ordering advertisement
|
|
1707
|
+
-- (bug-window-node-advertises-source-row-order) so the merge join above it does
|
|
1708
|
+
-- not drop rows.
|
|
1709
|
+
select k, (select min(t.b) from wsq t where t.a = k) as c, row_number() over (order by k desc) as rn from (select a as k from wsq) group by k;
|
|
1710
|
+
→ [{"k":"y","c":"q","rn":1},{"k":"x","c":"p","rn":2}]
|
|
1711
|
+
|
|
1712
|
+
-- The same three shapes with an EXISTS and with an IN correlated subquery, which
|
|
1713
|
+
-- route through rule-subquery-decorrelation instead.
|
|
1714
|
+
select a, exists (select 1 from wsq t where t.a = wsq.a and t.b = 'p') as e, count(*) over () as n from wsq group by a;
|
|
1715
|
+
→ [{"a":"x","e":true,"n":2},{"a":"y","e":false,"n":2}]
|
|
1716
|
+
|
|
1717
|
+
select a, exists (select 1 from wsq t where t.a = wsq.a and t.b = 'p') as e, row_number() over (order by a) as rn from wsq group by a;
|
|
1718
|
+
→ [{"a":"x","e":true,"rn":1},{"a":"y","e":false,"rn":2}]
|
|
1719
|
+
|
|
1720
|
+
select a, exists (select 1 from wsq t where t.a = wsq.a and t.b = 'p') as e, row_number() over (order by a desc) as rn from wsq group by a;
|
|
1721
|
+
→ [{"a":"y","e":false,"rn":1},{"a":"x","e":true,"rn":2}]
|
|
1722
|
+
|
|
1723
|
+
select a, 'p' in (select t.b from wsq t where t.a = wsq.a) as i, count(*) over () as n from wsq group by a;
|
|
1724
|
+
→ [{"a":"x","i":true,"n":2},{"a":"y","i":false,"n":2}]
|
|
1725
|
+
|
|
1726
|
+
select a, 'p' in (select t.b from wsq t where t.a = wsq.a) as i, row_number() over (order by a) as rn from wsq group by a;
|
|
1727
|
+
→ [{"a":"x","i":true,"rn":1},{"a":"y","i":false,"rn":2}]
|
|
1728
|
+
|
|
1729
|
+
select a, 'p' in (select t.b from wsq t where t.a = wsq.a) as i, row_number() over (order by a desc) as rn from wsq group by a;
|
|
1730
|
+
→ [{"a":"y","i":false,"rn":1},{"a":"x","i":true,"rn":2}]
|
|
1731
|
+
|
|
1732
|
+
-- UNGROUPED windowed query with a correlated subquery — was already correct (the
|
|
1733
|
+
-- streaming window path keeps the window's own row newest); regression guard.
|
|
1734
|
+
select a, (select min(t.b) from wsq t where t.a = wsq.a) as c, row_number() over (order by a) as rn from wsq order by rn;
|
|
1735
|
+
→ [{"a":"x","c":"p","rn":1},{"a":"x","c":"p","rn":2},{"a":"y","c":"q","rn":3}]
|
|
1736
|
+
|
|
1737
|
+
-- Two window functions with DIFFERENT specifications alongside the decorrelated
|
|
1738
|
+
-- subquery, so a mis-addressed read cannot be masked by both columns holding the
|
|
1739
|
+
-- same value.
|
|
1740
|
+
select k, (select min(t.b) from wsq t where t.a = k) as c, count(*) over () as n, row_number() over (order by k) as rn from (select a as k from wsq) group by k;
|
|
1741
|
+
→ [{"k":"x","c":"p","n":2,"rn":1},{"k":"y","c":"q","n":2,"rn":2}]
|
|
1742
|
+
|
|
1743
|
+
-- Two window functions with the SAME specification now genuinely share one
|
|
1744
|
+
-- WindowNode; each must still resolve to its own distinct output column — the
|
|
1745
|
+
-- case the old source-location accident in the spec grouping was protecting.
|
|
1746
|
+
select a, row_number() over (order by a) as rn, count(*) over (order by a) as rc from wsq group by a;
|
|
1747
|
+
→ [{"a":"x","rn":1,"rc":1},{"a":"y","rn":2,"rc":2}]
|
|
1748
|
+
|
|
1749
|
+
-- Same-NAME functions sharing one specification (and so one WindowNode) keep
|
|
1750
|
+
-- their own argument expressions.
|
|
1751
|
+
select b, sum(length(b)) over (order by b) as s1, sum(length(b) * 10) over (order by b) as s2 from wsq order by b;
|
|
1752
|
+
→ [{"b":"p","s1":1,"s2":10},{"b":"q","s1":2,"s2":20},{"b":"r","s1":3,"s2":30}]
|
|
1753
|
+
|
|
1754
|
+
-- A PARTITIONED window beside the decorrelated subquery: the partitioned buffered
|
|
1755
|
+
-- path emits whole partitions in first-seen order and advertises neither ordering
|
|
1756
|
+
-- nor monotonicity, so the join above it sees a different shape than the sorted
|
|
1757
|
+
-- cases above.
|
|
1758
|
+
select a, (select min(t.b) from wsq t where t.a = wsq.a) as c, count(*) over (partition by a) as n from wsq group by a;
|
|
1759
|
+
→ [{"a":"x","c":"p","n":1},{"a":"y","c":"q","n":1}]
|
|
1760
|
+
|
|
1761
|
+
-- Two functions with ARGUMENTS sharing one specification (so one WindowNode, whose
|
|
1762
|
+
-- per-function argument arrays must stay aligned with its output columns).
|
|
1763
|
+
select a, (select min(t.b) from wsq t where t.a = wsq.a) as c, lag(a) over (order by a) as lg, lead(a) over (order by a) as ld from wsq group by a;
|
|
1764
|
+
→ [{"a":"x","c":"p","lg":null,"ld":"y"},{"a":"y","c":"q","lg":"x","ld":null}]
|
|
1765
|
+
|
|
1766
|
+
-- A filter over a window output column through a subquery. The filter references
|
|
1767
|
+
-- a real window attribute the window's SOURCE does not carry, so the availability
|
|
1768
|
+
-- check must keep it above the WindowNode — a future pushdown rule that loses
|
|
1769
|
+
-- this returns [{"a":"x",...}] too.
|
|
1770
|
+
select * from (select a, row_number() over (order by a) as rn from wsq group by a) where rn > 1;
|
|
1771
|
+
→ [{"a":"y","rn":2}]
|
|
1772
|
+
|
|
1773
|
+
drop table wsq;
|
|
1774
|
+
|
|
1775
|
+
-- Composition: the fixed shape as a subquery source, inside a CTE, and as a
|
|
1776
|
+
-- `union all` arm.
|
|
1777
|
+
select * from (select wg.a, row_number() over (order by a) as rn from wg group by a) z order by rn;
|
|
1778
|
+
→ [{"a":"x","rn":1},{"a":"y","rn":2}]
|
|
1779
|
+
|
|
1780
|
+
with q as (select wg.a, row_number() over (order by a) as rn from wg group by a)
|
|
1781
|
+
select * from q where rn > 1;
|
|
1782
|
+
→ [{"a":"y","rn":2}]
|
|
1783
|
+
|
|
1784
|
+
select wg.a as k, row_number() over (order by a) as rn from wg group by a
|
|
1785
|
+
union all select b, 9 from wg order by k, rn;
|
|
1786
|
+
→ [{"k":"1","rn":9},{"k":"2","rn":9},{"k":"3","rn":9},{"k":"x","rn":1},{"k":"y","rn":2}]
|
|
1787
|
+
|
|
1788
|
+
-- An EMPTY grouped, windowed query: no group ever yields, so no representative
|
|
1789
|
+
-- source row is ever published. Exercises the path with the fallback absent.
|
|
1790
|
+
delete from wg;
|
|
1791
|
+
select wg.a, row_number() over (order by a) as rn from wg group by a;
|
|
1792
|
+
→ []
|
|
1793
|
+
|
|
1794
|
+
drop table wg;
|
|
1795
|
+
|
|
1796
|
+
-- A NULL grouping key forms one group of its own and must appear exactly once.
|
|
1797
|
+
create table wn (id integer primary key, a text null);
|
|
1798
|
+
insert into wn values (1,'x'),(2,null),(3,'x');
|
|
1799
|
+
|
|
1800
|
+
select wn.a, row_number() over (order by a) as rn from wn group by a order by rn;
|
|
1801
|
+
→ [{"a":null,"rn":1},{"a":"x","rn":2}]
|
|
1802
|
+
|
|
1803
|
+
select wn.a, count(*) as c from wn group by a order by c;
|
|
1804
|
+
→ [{"a":null,"c":1},{"a":"x","c":2}]
|
|
1805
|
+
|
|
1806
|
+
drop table wn;
|
|
1807
|
+
|
|
1808
|
+
-- Two grouping keys sharing a bare name across a join. Their QUALIFIED names are
|
|
1809
|
+
-- registered by the aggregate output scope, so those references resolve there and
|
|
1810
|
+
-- never reach the redirect; the bare name stays ambiguous. Pinned windowed and
|
|
1811
|
+
-- unwindowed so the redirect is proved not to have disturbed the ambiguity.
|
|
1812
|
+
create table ji (id text, v text);
|
|
1813
|
+
create table jc (id text, w text);
|
|
1814
|
+
insert into ji values ('k1','a'),('k2','b');
|
|
1815
|
+
insert into jc values ('k1','p'),('k2','q');
|
|
1816
|
+
|
|
1817
|
+
select i.id, c.id, row_number() over (order by i.id) as rn
|
|
1818
|
+
from ji i join jc c on i.id = c.id group by i.id, c.id order by rn;
|
|
1819
|
+
→ [{"id":"k1","id:1":"k1","rn":1},{"id":"k2","id:1":"k2","rn":2}]
|
|
1820
|
+
|
|
1821
|
+
select i.id, c.id from ji i join jc c on i.id = c.id group by i.id, c.id order by 1;
|
|
1822
|
+
→ [{"id":"k1","id:1":"k1"},{"id":"k2","id:1":"k2"}]
|
|
1823
|
+
|
|
1824
|
+
select id, row_number() over (order by i.id) as rn
|
|
1825
|
+
from ji i join jc c on i.id = c.id group by i.id, c.id;
|
|
1826
|
+
-- error: ambiguous column name: id
|
|
1827
|
+
|
|
1828
|
+
drop table ji;
|
|
1829
|
+
drop table jc;
|
|
1830
|
+
|
|
1831
|
+
-- =============================================================================
|
|
1832
|
+
-- `*` in a window query.
|
|
1833
|
+
--
|
|
1834
|
+
-- The window phase's projection used to walk the AST select list and handle only
|
|
1835
|
+
-- non-star entries, so a `*` was skipped outright and its columns never reached
|
|
1836
|
+
-- the output. The projection is now the query's ONE select-list projection, with
|
|
1837
|
+
-- stars already expanded, so a star is an ordinary entry.
|
|
1838
|
+
--
|
|
1839
|
+
-- Column NAMES and ORDER are asserted in test/plan/grouped-projection-shape.spec.ts
|
|
1840
|
+
-- (row objects compare key-order-insensitively here); this file pins the values.
|
|
1841
|
+
-- =============================================================================
|
|
1842
|
+
|
|
1843
|
+
create table sw (v integer primary key, g text);
|
|
1844
|
+
insert into sw values (1,'a'),(2,'b');
|
|
1845
|
+
|
|
1846
|
+
-- Star before the window column.
|
|
1847
|
+
select *, row_number() over (order by v) as w from sw order by v;
|
|
1848
|
+
→ [{"v":1,"g":"a","w":1},{"v":2,"g":"b","w":2}]
|
|
1849
|
+
|
|
1850
|
+
-- Star after the window column.
|
|
1851
|
+
select row_number() over (order by v) as w, * from sw order by v;
|
|
1852
|
+
→ [{"w":1,"v":1,"g":"a"},{"w":2,"v":2,"g":"b"}]
|
|
1853
|
+
|
|
1854
|
+
-- A named column repeated by a later star is disambiguated as `v:1`.
|
|
1855
|
+
select v, row_number() over (order by v) as w, * from sw order by w;
|
|
1856
|
+
→ [{"v":1,"w":1,"v:1":1,"g":"a"},{"v":2,"w":2,"v:1":2,"g":"b"}]
|
|
1857
|
+
|
|
1858
|
+
-- Qualified star.
|
|
1859
|
+
select sw.*, row_number() over (order by v) as w from sw order by v;
|
|
1860
|
+
→ [{"v":1,"g":"a","w":1},{"v":2,"g":"b","w":2}]
|
|
1861
|
+
|
|
1862
|
+
-- A positional ORDER BY over a star-bearing window query binds the window column
|
|
1863
|
+
-- (position 3 = the column after the two star columns).
|
|
1864
|
+
select *, row_number() over (order by v) as w from sw order by 3 desc;
|
|
1865
|
+
→ [{"v":2,"g":"b","w":2},{"v":1,"g":"a","w":1}]
|
|
1866
|
+
|
|
1867
|
+
-- DISTINCT over a window column.
|
|
1868
|
+
select distinct row_number() over (order by v) as w from sw order by w;
|
|
1869
|
+
→ [{"w":1},{"w":2}]
|
|
1870
|
+
|
|
1871
|
+
-- An UNALIASED window column keeps its authored expression as its output name,
|
|
1872
|
+
-- like every other unaliased select-list column, rather than the name of the
|
|
1873
|
+
-- window-output column the rewrite substitutes in.
|
|
1874
|
+
select row_number() over (order by v) from sw order by v;
|
|
1875
|
+
→ [{"row_number() over (order by v)":1},{"row_number() over (order by v)":2}]
|
|
1876
|
+
|
|
1877
|
+
select v, 1000 - row_number() over (order by v) from sw order by v;
|
|
1878
|
+
→ [{"v":1,"1000 - row_number() over (order by v)":999},{"v":2,"1000 - row_number() over (order by v)":998}]
|
|
1879
|
+
|
|
1880
|
+
drop table sw;
|
|
1881
|
+
|
|
1882
|
+
-- =============================================================================
|
|
1883
|
+
-- The window node's advertised row order must describe what the window emitter
|
|
1884
|
+
-- actually yields (bug-window-node-advertises-source-row-order). A window that
|
|
1885
|
+
-- sorts its own rows used to keep advertising the SOURCE's order, and a merge
|
|
1886
|
+
-- join above it walked the reordered stream as if it were still in that order —
|
|
1887
|
+
-- silently dropping every row after the first mismatch.
|
|
1888
|
+
-- =============================================================================
|
|
1889
|
+
|
|
1890
|
+
create table wmj (a text, b text);
|
|
1891
|
+
insert into wmj values ('x','p'),('y','q'),('x','r'),('z','s');
|
|
1892
|
+
|
|
1893
|
+
-- The repro: `order by a desc` inside the window reverses the grouped rows, so
|
|
1894
|
+
-- the join above it must not assume ascending `a`. All four rows must survive.
|
|
1895
|
+
select z.a, z.rn, w2.b
|
|
1896
|
+
from (select a, row_number() over (order by a desc) as rn from wmj group by a) z
|
|
1897
|
+
join wmj w2 on w2.a = z.a
|
|
1898
|
+
order by z.a, w2.b;
|
|
1899
|
+
→ [{"a":"x","rn":3,"b":"p"},{"a":"x","rn":3,"b":"r"},{"a":"y","rn":2,"b":"q"},{"a":"z","rn":1,"b":"s"}]
|
|
1900
|
+
|
|
1901
|
+
-- The ascending twin, which was already correct and must stay correct: here the
|
|
1902
|
+
-- window's emit order genuinely does match the source's.
|
|
1903
|
+
select z.a, z.rn, w2.b
|
|
1904
|
+
from (select a, row_number() over (order by a) as rn from wmj group by a) z
|
|
1905
|
+
join wmj w2 on w2.a = z.a
|
|
1906
|
+
order by z.a, w2.b;
|
|
1907
|
+
→ [{"a":"x","rn":1,"b":"p"},{"a":"x","rn":1,"b":"r"},{"a":"y","rn":2,"b":"q"},{"a":"z","rn":3,"b":"s"}]
|
|
1908
|
+
|
|
1909
|
+
-- Same shape as a LEFT JOIN: a lost match would show up as NULL fill rather
|
|
1910
|
+
-- than as a missing row.
|
|
1911
|
+
select z.a, z.rn, w2.b
|
|
1912
|
+
from (select a, row_number() over (order by a desc) as rn from wmj group by a) z
|
|
1913
|
+
left join wmj w2 on w2.a = z.a
|
|
1914
|
+
order by z.a, w2.b;
|
|
1915
|
+
→ [{"a":"x","rn":3,"b":"p"},{"a":"x","rn":3,"b":"r"},{"a":"y","rn":2,"b":"q"},{"a":"z","rn":1,"b":"s"}]
|
|
1916
|
+
|
|
1917
|
+
drop table wmj;
|
|
1918
|
+
|
|
1919
|
+
-- A PARTITION BY window buffers the source and re-emits it a whole partition at
|
|
1920
|
+
-- a time, so the scan's key order does NOT survive it. The outer ORDER BY must
|
|
1921
|
+
-- be honoured rather than elided against the source's ordering.
|
|
1922
|
+
create table wpo (k integer primary key, g text, v integer);
|
|
1923
|
+
insert into wpo values (1,'a',10),(2,'b',20),(3,'a',30),(4,'b',40);
|
|
1924
|
+
|
|
1925
|
+
select k, g, sum(v) over (partition by g) as s from wpo order by k;
|
|
1926
|
+
→ [{"k":1,"g":"a","s":40},{"k":2,"g":"b","s":60},{"k":3,"g":"a","s":40},{"k":4,"g":"b","s":60}]
|
|
1927
|
+
|
|
1928
|
+
-- Same, joined against the partition column: the join above the partitioned
|
|
1929
|
+
-- window may not assume the window preserved the scan's order either.
|
|
1930
|
+
select w.k, w.s, o.g
|
|
1931
|
+
from (select k, g, sum(v) over (partition by g) as s from wpo) w
|
|
1932
|
+
join wpo o on o.k = w.k
|
|
1933
|
+
order by w.k;
|
|
1934
|
+
→ [{"k":1,"s":40,"g":"a"},{"k":2,"s":60,"g":"b"},{"k":3,"s":40,"g":"a"},{"k":4,"s":60,"g":"b"}]
|
|
1935
|
+
|
|
1936
|
+
-- An unpartitioned window with a descending ORDER BY, consumed by an outer
|
|
1937
|
+
-- ascending ORDER BY: the outer sort must survive, since the window emits desc.
|
|
1938
|
+
select k, v, row_number() over (order by v desc) as rn from wpo order by k;
|
|
1939
|
+
→ [{"k":1,"v":10,"rn":4},{"k":2,"v":20,"rn":3},{"k":3,"v":30,"rn":2},{"k":4,"v":40,"rn":1}]
|
|
1940
|
+
|
|
1941
|
+
drop table wpo;
|