@quereus/quereus 3.2.1 → 4.0.0
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 +7 -0
- package/dist/src/common/datatype.d.ts +12 -0
- package/dist/src/common/datatype.d.ts.map +1 -1
- package/dist/src/common/datatype.js.map +1 -1
- package/dist/src/common/types.d.ts +24 -0
- package/dist/src/common/types.d.ts.map +1 -1
- package/dist/src/common/types.js.map +1 -1
- package/dist/src/core/database-assertions.d.ts +37 -9
- package/dist/src/core/database-assertions.d.ts.map +1 -1
- package/dist/src/core/database-assertions.js +62 -106
- package/dist/src/core/database-assertions.js.map +1 -1
- package/dist/src/core/database-events.d.ts +163 -0
- package/dist/src/core/database-events.d.ts.map +1 -1
- package/dist/src/core/database-events.js +235 -21
- package/dist/src/core/database-events.js.map +1 -1
- package/dist/src/core/database-external-changes.d.ts +28 -0
- package/dist/src/core/database-external-changes.d.ts.map +1 -0
- package/dist/src/core/database-external-changes.js +242 -0
- package/dist/src/core/database-external-changes.js.map +1 -0
- package/dist/src/core/database-internal.d.ts +50 -1
- package/dist/src/core/database-internal.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views.d.ts +1253 -0
- package/dist/src/core/database-materialized-views.d.ts.map +1 -0
- package/dist/src/core/database-materialized-views.js +3064 -0
- package/dist/src/core/database-materialized-views.js.map +1 -0
- package/dist/src/core/database-options.d.ts +4 -0
- package/dist/src/core/database-options.d.ts.map +1 -1
- package/dist/src/core/database-options.js +10 -0
- package/dist/src/core/database-options.js.map +1 -1
- package/dist/src/core/database-transaction.d.ts +19 -3
- package/dist/src/core/database-transaction.d.ts.map +1 -1
- package/dist/src/core/database-transaction.js +30 -3
- package/dist/src/core/database-transaction.js.map +1 -1
- package/dist/src/core/database-watchers.d.ts +19 -0
- package/dist/src/core/database-watchers.d.ts.map +1 -1
- package/dist/src/core/database-watchers.js +63 -3
- package/dist/src/core/database-watchers.js.map +1 -1
- package/dist/src/core/database.d.ts +203 -11
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +493 -29
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/core/derived-row-validator.d.ts +137 -0
- package/dist/src/core/derived-row-validator.d.ts.map +1 -0
- package/dist/src/core/derived-row-validator.js +314 -0
- package/dist/src/core/derived-row-validator.js.map +1 -0
- package/dist/src/core/statement.d.ts.map +1 -1
- package/dist/src/core/statement.js +30 -9
- package/dist/src/core/statement.js.map +1 -1
- package/dist/src/emit/ast-stringify.d.ts +135 -1
- package/dist/src/emit/ast-stringify.d.ts.map +1 -1
- package/dist/src/emit/ast-stringify.js +795 -120
- package/dist/src/emit/ast-stringify.js.map +1 -1
- package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
- package/dist/src/func/builtins/aggregate.js +11 -10
- package/dist/src/func/builtins/aggregate.js.map +1 -1
- package/dist/src/func/builtins/builtin-window-functions.d.ts.map +1 -1
- package/dist/src/func/builtins/builtin-window-functions.js +32 -0
- package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
- package/dist/src/func/builtins/explain.d.ts +3 -0
- package/dist/src/func/builtins/explain.d.ts.map +1 -1
- package/dist/src/func/builtins/explain.js +229 -0
- package/dist/src/func/builtins/explain.js.map +1 -1
- package/dist/src/func/builtins/index.d.ts.map +1 -1
- package/dist/src/func/builtins/index.js +10 -2
- package/dist/src/func/builtins/index.js.map +1 -1
- package/dist/src/func/builtins/json.d.ts.map +1 -1
- package/dist/src/func/builtins/json.js +3 -2
- package/dist/src/func/builtins/json.js.map +1 -1
- package/dist/src/func/builtins/mutation.d.ts +2 -0
- package/dist/src/func/builtins/mutation.d.ts.map +1 -0
- package/dist/src/func/builtins/mutation.js +53 -0
- package/dist/src/func/builtins/mutation.js.map +1 -0
- package/dist/src/func/builtins/schema.d.ts +2 -0
- package/dist/src/func/builtins/schema.d.ts.map +1 -1
- package/dist/src/func/builtins/schema.js +713 -26
- package/dist/src/func/builtins/schema.js.map +1 -1
- package/dist/src/func/builtins/string.js +1 -1
- package/dist/src/func/builtins/string.js.map +1 -1
- package/dist/src/func/registration.d.ts +9 -0
- package/dist/src/func/registration.d.ts.map +1 -1
- package/dist/src/func/registration.js +4 -0
- package/dist/src/func/registration.js.map +1 -1
- package/dist/src/index.d.ts +25 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +27 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/parser/ast.d.ts +353 -21
- package/dist/src/parser/ast.d.ts.map +1 -1
- package/dist/src/parser/index.d.ts +14 -1
- package/dist/src/parser/index.d.ts.map +1 -1
- package/dist/src/parser/index.js +19 -0
- package/dist/src/parser/index.js.map +1 -1
- package/dist/src/parser/lexer.d.ts +9 -0
- package/dist/src/parser/lexer.d.ts.map +1 -1
- package/dist/src/parser/lexer.js +9 -0
- package/dist/src/parser/lexer.js.map +1 -1
- package/dist/src/parser/parser.d.ts +277 -8
- package/dist/src/parser/parser.d.ts.map +1 -1
- package/dist/src/parser/parser.js +1393 -471
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/src/parser/visitor.d.ts.map +1 -1
- package/dist/src/parser/visitor.js +12 -8
- package/dist/src/parser/visitor.js.map +1 -1
- package/dist/src/planner/analysis/assertion-classifier.d.ts.map +1 -1
- package/dist/src/planner/analysis/assertion-classifier.js +4 -0
- package/dist/src/planner/analysis/assertion-classifier.js.map +1 -1
- package/dist/src/planner/analysis/assertion-hoist-cache.d.ts.map +1 -1
- package/dist/src/planner/analysis/assertion-hoist-cache.js +8 -4
- package/dist/src/planner/analysis/assertion-hoist-cache.js.map +1 -1
- package/dist/src/planner/analysis/authored-inverse.d.ts +22 -0
- package/dist/src/planner/analysis/authored-inverse.d.ts.map +1 -0
- package/dist/src/planner/analysis/authored-inverse.js +267 -0
- package/dist/src/planner/analysis/authored-inverse.js.map +1 -0
- package/dist/src/planner/analysis/binding-extractor.d.ts.map +1 -1
- package/dist/src/planner/analysis/binding-extractor.js +9 -6
- package/dist/src/planner/analysis/binding-extractor.js.map +1 -1
- package/dist/src/planner/analysis/change-scope.d.ts +34 -4
- package/dist/src/planner/analysis/change-scope.d.ts.map +1 -1
- package/dist/src/planner/analysis/change-scope.js +115 -7
- package/dist/src/planner/analysis/change-scope.js.map +1 -1
- package/dist/src/planner/analysis/check-extraction.d.ts +36 -2
- package/dist/src/planner/analysis/check-extraction.d.ts.map +1 -1
- package/dist/src/planner/analysis/check-extraction.js +174 -46
- package/dist/src/planner/analysis/check-extraction.js.map +1 -1
- package/dist/src/planner/analysis/coarsened-key.d.ts +109 -0
- package/dist/src/planner/analysis/coarsened-key.d.ts.map +1 -0
- package/dist/src/planner/analysis/coarsened-key.js +228 -0
- package/dist/src/planner/analysis/coarsened-key.js.map +1 -0
- package/dist/src/planner/analysis/comparison-collation.d.ts +216 -0
- package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -0
- package/dist/src/planner/analysis/comparison-collation.js +341 -0
- package/dist/src/planner/analysis/comparison-collation.js.map +1 -0
- package/dist/src/planner/analysis/constraint-extractor.d.ts +13 -1
- package/dist/src/planner/analysis/constraint-extractor.d.ts.map +1 -1
- package/dist/src/planner/analysis/constraint-extractor.js +220 -21
- package/dist/src/planner/analysis/constraint-extractor.js.map +1 -1
- package/dist/src/planner/analysis/coverage-prover.d.ts +321 -0
- package/dist/src/planner/analysis/coverage-prover.d.ts.map +1 -0
- package/dist/src/planner/analysis/coverage-prover.js +1038 -0
- package/dist/src/planner/analysis/coverage-prover.js.map +1 -0
- package/dist/src/planner/analysis/key-filter.d.ts +22 -0
- package/dist/src/planner/analysis/key-filter.d.ts.map +1 -0
- package/dist/src/planner/analysis/key-filter.js +105 -0
- package/dist/src/planner/analysis/key-filter.js.map +1 -0
- package/dist/src/planner/analysis/partial-unique-extraction.d.ts +36 -1
- package/dist/src/planner/analysis/partial-unique-extraction.d.ts.map +1 -1
- package/dist/src/planner/analysis/partial-unique-extraction.js +148 -22
- package/dist/src/planner/analysis/partial-unique-extraction.js.map +1 -1
- package/dist/src/planner/analysis/predicate-normalizer.d.ts.map +1 -1
- package/dist/src/planner/analysis/predicate-normalizer.js +30 -1
- package/dist/src/planner/analysis/predicate-normalizer.js.map +1 -1
- package/dist/src/planner/analysis/predicate-shape.d.ts +36 -1
- package/dist/src/planner/analysis/predicate-shape.d.ts.map +1 -1
- package/dist/src/planner/analysis/predicate-shape.js +51 -13
- package/dist/src/planner/analysis/predicate-shape.js.map +1 -1
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +314 -0
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -0
- package/dist/src/planner/analysis/query-rewrite-matcher.js +1081 -0
- package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -0
- package/dist/src/planner/analysis/scalar-invertibility.d.ts +92 -0
- package/dist/src/planner/analysis/scalar-invertibility.d.ts.map +1 -0
- package/dist/src/planner/analysis/scalar-invertibility.js +129 -0
- package/dist/src/planner/analysis/scalar-invertibility.js.map +1 -0
- package/dist/src/planner/analysis/update-lineage.d.ts +196 -0
- package/dist/src/planner/analysis/update-lineage.d.ts.map +1 -0
- package/dist/src/planner/analysis/update-lineage.js +322 -0
- package/dist/src/planner/analysis/update-lineage.js.map +1 -0
- package/dist/src/planner/analysis/view-complement.d.ts +42 -0
- package/dist/src/planner/analysis/view-complement.d.ts.map +1 -0
- package/dist/src/planner/analysis/view-complement.js +54 -0
- package/dist/src/planner/analysis/view-complement.js.map +1 -0
- package/dist/src/planner/building/alter-table.d.ts +1 -1
- package/dist/src/planner/building/alter-table.d.ts.map +1 -1
- package/dist/src/planner/building/alter-table.js +211 -2
- package/dist/src/planner/building/alter-table.js.map +1 -1
- package/dist/src/planner/building/block.d.ts.map +1 -1
- package/dist/src/planner/building/block.js +18 -1
- package/dist/src/planner/building/block.js.map +1 -1
- package/dist/src/planner/building/constraint-builder.d.ts +33 -5
- package/dist/src/planner/building/constraint-builder.d.ts.map +1 -1
- package/dist/src/planner/building/constraint-builder.js +63 -28
- package/dist/src/planner/building/constraint-builder.js.map +1 -1
- package/dist/src/planner/building/create-view.d.ts +9 -0
- package/dist/src/planner/building/create-view.d.ts.map +1 -1
- package/dist/src/planner/building/create-view.js +41 -12
- package/dist/src/planner/building/create-view.js.map +1 -1
- package/dist/src/planner/building/ddl.d.ts.map +1 -1
- package/dist/src/planner/building/ddl.js +94 -0
- package/dist/src/planner/building/ddl.js.map +1 -1
- package/dist/src/planner/building/declare-schema.d.ts +1 -0
- package/dist/src/planner/building/declare-schema.d.ts.map +1 -1
- package/dist/src/planner/building/declare-schema.js +4 -1
- package/dist/src/planner/building/declare-schema.js.map +1 -1
- package/dist/src/planner/building/default-scope.d.ts +26 -0
- package/dist/src/planner/building/default-scope.d.ts.map +1 -0
- package/dist/src/planner/building/default-scope.js +41 -0
- package/dist/src/planner/building/default-scope.js.map +1 -0
- package/dist/src/planner/building/delete.d.ts +19 -1
- package/dist/src/planner/building/delete.d.ts.map +1 -1
- package/dist/src/planner/building/delete.js +116 -34
- package/dist/src/planner/building/delete.js.map +1 -1
- package/dist/src/planner/building/dml-target.d.ts +118 -0
- package/dist/src/planner/building/dml-target.d.ts.map +1 -0
- package/dist/src/planner/building/dml-target.js +282 -0
- package/dist/src/planner/building/dml-target.js.map +1 -0
- package/dist/src/planner/building/drop-index.d.ts.map +1 -1
- package/dist/src/planner/building/drop-index.js +4 -1
- package/dist/src/planner/building/drop-index.js.map +1 -1
- package/dist/src/planner/building/drop-view.d.ts.map +1 -1
- package/dist/src/planner/building/drop-view.js +4 -2
- package/dist/src/planner/building/drop-view.js.map +1 -1
- package/dist/src/planner/building/expression.d.ts.map +1 -1
- package/dist/src/planner/building/expression.js +60 -21
- package/dist/src/planner/building/expression.js.map +1 -1
- package/dist/src/planner/building/foreign-key-builder.d.ts +30 -0
- package/dist/src/planner/building/foreign-key-builder.d.ts.map +1 -1
- package/dist/src/planner/building/foreign-key-builder.js +160 -129
- package/dist/src/planner/building/foreign-key-builder.js.map +1 -1
- package/dist/src/planner/building/insert.d.ts +45 -2
- package/dist/src/planner/building/insert.d.ts.map +1 -1
- package/dist/src/planner/building/insert.js +257 -88
- package/dist/src/planner/building/insert.js.map +1 -1
- package/dist/src/planner/building/lens-auxiliary-access.d.ts +22 -0
- package/dist/src/planner/building/lens-auxiliary-access.d.ts.map +1 -0
- package/dist/src/planner/building/lens-auxiliary-access.js +132 -0
- package/dist/src/planner/building/lens-auxiliary-access.js.map +1 -0
- package/dist/src/planner/building/materialized-view.d.ts +16 -0
- package/dist/src/planner/building/materialized-view.d.ts.map +1 -0
- package/dist/src/planner/building/materialized-view.js +57 -0
- package/dist/src/planner/building/materialized-view.js.map +1 -0
- package/dist/src/planner/building/returning-star.d.ts +32 -0
- package/dist/src/planner/building/returning-star.d.ts.map +1 -0
- package/dist/src/planner/building/returning-star.js +45 -0
- package/dist/src/planner/building/returning-star.js.map +1 -0
- package/dist/src/planner/building/select-aggregates.d.ts.map +1 -1
- package/dist/src/planner/building/select-aggregates.js +51 -13
- package/dist/src/planner/building/select-aggregates.js.map +1 -1
- package/dist/src/planner/building/select-compound.d.ts.map +1 -1
- package/dist/src/planner/building/select-compound.js +84 -11
- package/dist/src/planner/building/select-compound.js.map +1 -1
- package/dist/src/planner/building/select-context.d.ts +10 -2
- package/dist/src/planner/building/select-context.d.ts.map +1 -1
- package/dist/src/planner/building/select-context.js +7 -1
- package/dist/src/planner/building/select-context.js.map +1 -1
- package/dist/src/planner/building/select-modifiers.js +6 -0
- package/dist/src/planner/building/select-modifiers.js.map +1 -1
- package/dist/src/planner/building/select-ordinal.d.ts +18 -0
- package/dist/src/planner/building/select-ordinal.d.ts.map +1 -1
- package/dist/src/planner/building/select-ordinal.js +30 -0
- package/dist/src/planner/building/select-ordinal.js.map +1 -1
- package/dist/src/planner/building/select-projections.d.ts +8 -2
- package/dist/src/planner/building/select-projections.d.ts.map +1 -1
- package/dist/src/planner/building/select-projections.js +26 -4
- package/dist/src/planner/building/select-projections.js.map +1 -1
- package/dist/src/planner/building/select-window.d.ts.map +1 -1
- package/dist/src/planner/building/select-window.js +8 -5
- package/dist/src/planner/building/select-window.js.map +1 -1
- package/dist/src/planner/building/select.d.ts.map +1 -1
- package/dist/src/planner/building/select.js +164 -59
- package/dist/src/planner/building/select.js.map +1 -1
- package/dist/src/planner/building/set-object-tags.d.ts +7 -0
- package/dist/src/planner/building/set-object-tags.d.ts.map +1 -0
- package/dist/src/planner/building/set-object-tags.js +38 -0
- package/dist/src/planner/building/set-object-tags.js.map +1 -0
- package/dist/src/planner/building/tag-diagnostics.d.ts +27 -0
- package/dist/src/planner/building/tag-diagnostics.d.ts.map +1 -0
- package/dist/src/planner/building/tag-diagnostics.js +37 -0
- package/dist/src/planner/building/tag-diagnostics.js.map +1 -0
- package/dist/src/planner/building/update.d.ts +18 -1
- package/dist/src/planner/building/update.d.ts.map +1 -1
- package/dist/src/planner/building/update.js +134 -58
- package/dist/src/planner/building/update.js.map +1 -1
- package/dist/src/planner/building/view-mutation-builder.d.ts +15 -0
- package/dist/src/planner/building/view-mutation-builder.d.ts.map +1 -0
- package/dist/src/planner/building/view-mutation-builder.js +1158 -0
- package/dist/src/planner/building/view-mutation-builder.js.map +1 -0
- package/dist/src/planner/building/with.d.ts +11 -0
- package/dist/src/planner/building/with.d.ts.map +1 -1
- package/dist/src/planner/building/with.js +48 -10
- package/dist/src/planner/building/with.js.map +1 -1
- package/dist/src/planner/cost/index.d.ts +83 -0
- package/dist/src/planner/cost/index.d.ts.map +1 -1
- package/dist/src/planner/cost/index.js +114 -0
- package/dist/src/planner/cost/index.js.map +1 -1
- package/dist/src/planner/framework/characteristics.d.ts +38 -4
- package/dist/src/planner/framework/characteristics.d.ts.map +1 -1
- package/dist/src/planner/framework/characteristics.js +50 -6
- package/dist/src/planner/framework/characteristics.js.map +1 -1
- package/dist/src/planner/framework/pass.d.ts.map +1 -1
- package/dist/src/planner/framework/pass.js +2 -1
- package/dist/src/planner/framework/pass.js.map +1 -1
- package/dist/src/planner/framework/physical-utils.d.ts.map +1 -1
- package/dist/src/planner/framework/physical-utils.js +7 -1
- package/dist/src/planner/framework/physical-utils.js.map +1 -1
- package/dist/src/planner/framework/registry.d.ts +39 -1
- package/dist/src/planner/framework/registry.d.ts.map +1 -1
- package/dist/src/planner/framework/registry.js +18 -2
- package/dist/src/planner/framework/registry.js.map +1 -1
- package/dist/src/planner/mutation/backward-body.d.ts +131 -0
- package/dist/src/planner/mutation/backward-body.d.ts.map +1 -0
- package/dist/src/planner/mutation/backward-body.js +135 -0
- package/dist/src/planner/mutation/backward-body.js.map +1 -0
- package/dist/src/planner/mutation/cte-flatten.d.ts +17 -0
- package/dist/src/planner/mutation/cte-flatten.d.ts.map +1 -0
- package/dist/src/planner/mutation/cte-flatten.js +364 -0
- package/dist/src/planner/mutation/cte-flatten.js.map +1 -0
- package/dist/src/planner/mutation/decomposition.d.ts +273 -0
- package/dist/src/planner/mutation/decomposition.d.ts.map +1 -0
- package/dist/src/planner/mutation/decomposition.js +1719 -0
- package/dist/src/planner/mutation/decomposition.js.map +1 -0
- package/dist/src/planner/mutation/lens-enforcement.d.ts +165 -0
- package/dist/src/planner/mutation/lens-enforcement.d.ts.map +1 -0
- package/dist/src/planner/mutation/lens-enforcement.js +745 -0
- package/dist/src/planner/mutation/lens-enforcement.js.map +1 -0
- package/dist/src/planner/mutation/multi-source.d.ts +568 -0
- package/dist/src/planner/mutation/multi-source.d.ts.map +1 -0
- package/dist/src/planner/mutation/multi-source.js +2915 -0
- package/dist/src/planner/mutation/multi-source.js.map +1 -0
- package/dist/src/planner/mutation/mutation-diagnostic.d.ts +37 -0
- package/dist/src/planner/mutation/mutation-diagnostic.d.ts.map +1 -0
- package/dist/src/planner/mutation/mutation-diagnostic.js +24 -0
- package/dist/src/planner/mutation/mutation-diagnostic.js.map +1 -0
- package/dist/src/planner/mutation/mutation-tags.d.ts +33 -0
- package/dist/src/planner/mutation/mutation-tags.d.ts.map +1 -0
- package/dist/src/planner/mutation/mutation-tags.js +31 -0
- package/dist/src/planner/mutation/mutation-tags.js.map +1 -0
- package/dist/src/planner/mutation/propagate.d.ts +97 -0
- package/dist/src/planner/mutation/propagate.d.ts.map +1 -0
- package/dist/src/planner/mutation/propagate.js +220 -0
- package/dist/src/planner/mutation/propagate.js.map +1 -0
- package/dist/src/planner/mutation/scope-transform.d.ts +181 -0
- package/dist/src/planner/mutation/scope-transform.d.ts.map +1 -0
- package/dist/src/planner/mutation/scope-transform.js +574 -0
- package/dist/src/planner/mutation/scope-transform.js.map +1 -0
- package/dist/src/planner/mutation/set-op.d.ts +242 -0
- package/dist/src/planner/mutation/set-op.d.ts.map +1 -0
- package/dist/src/planner/mutation/set-op.js +1687 -0
- package/dist/src/planner/mutation/set-op.js.map +1 -0
- package/dist/src/planner/mutation/single-source.d.ts +261 -0
- package/dist/src/planner/mutation/single-source.d.ts.map +1 -0
- package/dist/src/planner/mutation/single-source.js +1096 -0
- package/dist/src/planner/mutation/single-source.js.map +1 -0
- package/dist/src/planner/nodes/aggregate-node.d.ts +6 -4
- package/dist/src/planner/nodes/aggregate-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/aggregate-node.js +11 -9
- package/dist/src/planner/nodes/aggregate-node.js.map +1 -1
- package/dist/src/planner/nodes/alias-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/alias-node.js +5 -1
- package/dist/src/planner/nodes/alias-node.js.map +1 -1
- package/dist/src/planner/nodes/alter-table-node.d.ts +124 -1
- package/dist/src/planner/nodes/alter-table-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/alter-table-node.js +27 -0
- package/dist/src/planner/nodes/alter-table-node.js.map +1 -1
- package/dist/src/planner/nodes/analyze-node.d.ts +2 -1
- package/dist/src/planner/nodes/analyze-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/analyze-node.js +21 -1
- package/dist/src/planner/nodes/analyze-node.js.map +1 -1
- package/dist/src/planner/nodes/asserted-keys-node.d.ts +43 -0
- package/dist/src/planner/nodes/asserted-keys-node.d.ts.map +1 -0
- package/dist/src/planner/nodes/asserted-keys-node.js +99 -0
- package/dist/src/planner/nodes/asserted-keys-node.js.map +1 -0
- package/dist/src/planner/nodes/async-gather-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/async-gather-node.js +33 -8
- package/dist/src/planner/nodes/async-gather-node.js.map +1 -1
- package/dist/src/planner/nodes/bloom-join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/bloom-join-node.js +2 -1
- package/dist/src/planner/nodes/bloom-join-node.js.map +1 -1
- package/dist/src/planner/nodes/create-view-node.d.ts +7 -2
- package/dist/src/planner/nodes/create-view-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/create-view-node.js +4 -1
- package/dist/src/planner/nodes/create-view-node.js.map +1 -1
- package/dist/src/planner/nodes/declarative-schema.d.ts +13 -1
- package/dist/src/planner/nodes/declarative-schema.d.ts.map +1 -1
- package/dist/src/planner/nodes/declarative-schema.js +32 -0
- package/dist/src/planner/nodes/declarative-schema.js.map +1 -1
- package/dist/src/planner/nodes/distinct-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/distinct-node.js +2 -0
- package/dist/src/planner/nodes/distinct-node.js.map +1 -1
- package/dist/src/planner/nodes/dml-executor-node.d.ts +29 -1
- package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/dml-executor-node.js +27 -3
- package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
- package/dist/src/planner/nodes/eager-prefetch-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/eager-prefetch-node.js +2 -0
- package/dist/src/planner/nodes/eager-prefetch-node.js.map +1 -1
- package/dist/src/planner/nodes/envelope-scan-node.d.ts +42 -0
- package/dist/src/planner/nodes/envelope-scan-node.d.ts.map +1 -0
- package/dist/src/planner/nodes/envelope-scan-node.js +62 -0
- package/dist/src/planner/nodes/envelope-scan-node.js.map +1 -0
- package/dist/src/planner/nodes/fanout-lookup-join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/fanout-lookup-join-node.js +11 -1
- package/dist/src/planner/nodes/fanout-lookup-join-node.js.map +1 -1
- package/dist/src/planner/nodes/filter.d.ts.map +1 -1
- package/dist/src/planner/nodes/filter.js +63 -13
- package/dist/src/planner/nodes/filter.js.map +1 -1
- package/dist/src/planner/nodes/hash-aggregate.d.ts.map +1 -1
- package/dist/src/planner/nodes/hash-aggregate.js +6 -16
- package/dist/src/planner/nodes/hash-aggregate.js.map +1 -1
- package/dist/src/planner/nodes/join-node.d.ts +41 -1
- package/dist/src/planner/nodes/join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/join-node.js +78 -8
- package/dist/src/planner/nodes/join-node.js.map +1 -1
- package/dist/src/planner/nodes/join-utils.d.ts +33 -6
- package/dist/src/planner/nodes/join-utils.d.ts.map +1 -1
- package/dist/src/planner/nodes/join-utils.js +131 -10
- package/dist/src/planner/nodes/join-utils.js.map +1 -1
- package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts +104 -0
- package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts.map +1 -0
- package/dist/src/planner/nodes/lens-auxiliary-access-node.js +91 -0
- package/dist/src/planner/nodes/lens-auxiliary-access-node.js.map +1 -0
- package/dist/src/planner/nodes/limit-offset.d.ts +12 -0
- package/dist/src/planner/nodes/limit-offset.d.ts.map +1 -1
- package/dist/src/planner/nodes/limit-offset.js +52 -3
- package/dist/src/planner/nodes/limit-offset.js.map +1 -1
- package/dist/src/planner/nodes/materialized-view-nodes.d.ts +69 -0
- package/dist/src/planner/nodes/materialized-view-nodes.d.ts.map +1 -0
- package/dist/src/planner/nodes/materialized-view-nodes.js +111 -0
- package/dist/src/planner/nodes/materialized-view-nodes.js.map +1 -0
- package/dist/src/planner/nodes/merge-join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/merge-join-node.js +2 -1
- package/dist/src/planner/nodes/merge-join-node.js.map +1 -1
- package/dist/src/planner/nodes/ordinal-slice-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/ordinal-slice-node.js +2 -0
- package/dist/src/planner/nodes/ordinal-slice-node.js.map +1 -1
- package/dist/src/planner/nodes/plan-node-type.d.ts +9 -0
- package/dist/src/planner/nodes/plan-node-type.d.ts.map +1 -1
- package/dist/src/planner/nodes/plan-node-type.js +9 -0
- package/dist/src/planner/nodes/plan-node-type.js.map +1 -1
- package/dist/src/planner/nodes/plan-node.d.ts +265 -5
- package/dist/src/planner/nodes/plan-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/plan-node.js.map +1 -1
- package/dist/src/planner/nodes/pragma.d.ts +2 -1
- package/dist/src/planner/nodes/pragma.d.ts.map +1 -1
- package/dist/src/planner/nodes/pragma.js +12 -0
- package/dist/src/planner/nodes/pragma.js.map +1 -1
- package/dist/src/planner/nodes/project-node.d.ts +14 -1
- package/dist/src/planner/nodes/project-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/project-node.js +103 -16
- package/dist/src/planner/nodes/project-node.js.map +1 -1
- package/dist/src/planner/nodes/reference.d.ts.map +1 -1
- package/dist/src/planner/nodes/reference.js +63 -30
- package/dist/src/planner/nodes/reference.js.map +1 -1
- package/dist/src/planner/nodes/retrieve-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/retrieve-node.js +7 -0
- package/dist/src/planner/nodes/retrieve-node.js.map +1 -1
- package/dist/src/planner/nodes/returning-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/returning-node.js +10 -3
- package/dist/src/planner/nodes/returning-node.js.map +1 -1
- package/dist/src/planner/nodes/scalar.d.ts +20 -0
- package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
- package/dist/src/planner/nodes/scalar.js +71 -14
- package/dist/src/planner/nodes/scalar.js.map +1 -1
- package/dist/src/planner/nodes/set-object-tags-node.d.ts +39 -0
- package/dist/src/planner/nodes/set-object-tags-node.d.ts.map +1 -0
- package/dist/src/planner/nodes/set-object-tags-node.js +41 -0
- package/dist/src/planner/nodes/set-object-tags-node.js.map +1 -0
- package/dist/src/planner/nodes/set-operation-node.d.ts +123 -1
- package/dist/src/planner/nodes/set-operation-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/set-operation-node.js +302 -18
- package/dist/src/planner/nodes/set-operation-node.js.map +1 -1
- package/dist/src/planner/nodes/single-row.d.ts.map +1 -1
- package/dist/src/planner/nodes/single-row.js +3 -0
- package/dist/src/planner/nodes/single-row.js.map +1 -1
- package/dist/src/planner/nodes/sort.d.ts.map +1 -1
- package/dist/src/planner/nodes/sort.js +8 -7
- package/dist/src/planner/nodes/sort.js.map +1 -1
- package/dist/src/planner/nodes/stream-aggregate.d.ts.map +1 -1
- package/dist/src/planner/nodes/stream-aggregate.js +8 -23
- package/dist/src/planner/nodes/stream-aggregate.js.map +1 -1
- package/dist/src/planner/nodes/subquery.d.ts +2 -0
- package/dist/src/planner/nodes/subquery.d.ts.map +1 -1
- package/dist/src/planner/nodes/subquery.js +18 -2
- package/dist/src/planner/nodes/subquery.js.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.js +23 -3
- package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
- package/dist/src/planner/nodes/table-function-call.js +6 -0
- package/dist/src/planner/nodes/table-function-call.js.map +1 -1
- package/dist/src/planner/nodes/values-node.d.ts +3 -1
- package/dist/src/planner/nodes/values-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/values-node.js +26 -0
- package/dist/src/planner/nodes/values-node.js.map +1 -1
- package/dist/src/planner/nodes/view-mutation-node.d.ts +259 -0
- package/dist/src/planner/nodes/view-mutation-node.d.ts.map +1 -0
- package/dist/src/planner/nodes/view-mutation-node.js +273 -0
- package/dist/src/planner/nodes/view-mutation-node.js.map +1 -0
- package/dist/src/planner/nodes/window-function.d.ts +17 -1
- package/dist/src/planner/nodes/window-function.d.ts.map +1 -1
- package/dist/src/planner/nodes/window-function.js +15 -1
- package/dist/src/planner/nodes/window-function.js.map +1 -1
- package/dist/src/planner/nodes/window-node.js +3 -3
- package/dist/src/planner/nodes/window-node.js.map +1 -1
- package/dist/src/planner/optimizer.d.ts.map +1 -1
- package/dist/src/planner/optimizer.js +372 -39
- package/dist/src/planner/optimizer.js.map +1 -1
- package/dist/src/planner/planning-context.d.ts +1 -1
- package/dist/src/planner/planning-context.d.ts.map +1 -1
- package/dist/src/planner/rules/access/lens-access-form-matcher.d.ts +70 -0
- package/dist/src/planner/rules/access/lens-access-form-matcher.d.ts.map +1 -0
- package/dist/src/planner/rules/access/lens-access-form-matcher.js +156 -0
- package/dist/src/planner/rules/access/lens-access-form-matcher.js.map +1 -0
- package/dist/src/planner/rules/access/rule-lens-auxiliary-access.d.ts +31 -0
- package/dist/src/planner/rules/access/rule-lens-auxiliary-access.d.ts.map +1 -0
- package/dist/src/planner/rules/access/rule-lens-auxiliary-access.js +176 -0
- package/dist/src/planner/rules/access/rule-lens-auxiliary-access.js.map +1 -0
- package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.js +435 -37
- package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
- package/dist/src/planner/rules/aggregate/rule-aggregate-streaming.d.ts.map +1 -1
- package/dist/src/planner/rules/aggregate/rule-aggregate-streaming.js +8 -27
- package/dist/src/planner/rules/aggregate/rule-aggregate-streaming.js.map +1 -1
- package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.d.ts +9 -3
- package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.d.ts.map +1 -1
- package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js +56 -5
- package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts +39 -0
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts.map +1 -0
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +616 -0
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -0
- package/dist/src/planner/rules/cache/rule-scalar-cse.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-scalar-cse.js +8 -1
- package/dist/src/planner/rules/cache/rule-scalar-cse.js.map +1 -1
- package/dist/src/planner/rules/distinct/rule-distinct-elimination.d.ts +8 -7
- package/dist/src/planner/rules/distinct/rule-distinct-elimination.d.ts.map +1 -1
- package/dist/src/planner/rules/distinct/rule-distinct-elimination.js +14 -21
- package/dist/src/planner/rules/distinct/rule-distinct-elimination.js.map +1 -1
- package/dist/src/planner/rules/join/equi-pair-extractor.d.ts +36 -0
- package/dist/src/planner/rules/join/equi-pair-extractor.d.ts.map +1 -1
- package/dist/src/planner/rules/join/equi-pair-extractor.js +42 -5
- package/dist/src/planner/rules/join/equi-pair-extractor.js.map +1 -1
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.js +10 -0
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.js.map +1 -1
- package/dist/src/planner/rules/join/rule-fanout-lookup-join.js +25 -9
- package/dist/src/planner/rules/join/rule-fanout-lookup-join.js.map +1 -1
- package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.d.ts +130 -0
- package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.d.ts.map +1 -0
- package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.js +206 -0
- package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.js.map +1 -0
- package/dist/src/planner/rules/join/rule-join-elimination.d.ts +67 -14
- package/dist/src/planner/rules/join/rule-join-elimination.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-join-elimination.js +81 -25
- package/dist/src/planner/rules/join/rule-join-elimination.js.map +1 -1
- package/dist/src/planner/rules/join/rule-join-existence-pruning.d.ts +84 -0
- package/dist/src/planner/rules/join/rule-join-existence-pruning.d.ts.map +1 -0
- package/dist/src/planner/rules/join/rule-join-existence-pruning.js +138 -0
- package/dist/src/planner/rules/join/rule-join-existence-pruning.js.map +1 -0
- package/dist/src/planner/rules/join/rule-join-greedy-commute.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-join-greedy-commute.js +19 -1
- package/dist/src/planner/rules/join/rule-join-greedy-commute.js.map +1 -1
- package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-join-physical-selection.js +14 -2
- package/dist/src/planner/rules/join/rule-join-physical-selection.js.map +1 -1
- package/dist/src/planner/rules/join/rule-lateral-top1-asof.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-lateral-top1-asof.js +5 -2
- package/dist/src/planner/rules/join/rule-lateral-top1-asof.js.map +1 -1
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.js +4 -0
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.js.map +1 -1
- package/dist/src/planner/rules/join/rule-quickpick-enumeration.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-quickpick-enumeration.js +10 -0
- package/dist/src/planner/rules/join/rule-quickpick-enumeration.js.map +1 -1
- package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.d.ts +286 -0
- package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.d.ts.map +1 -0
- package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.js +548 -0
- package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.js.map +1 -0
- package/dist/src/planner/rules/parallel/rule-async-gather-union-all.d.ts.map +1 -1
- package/dist/src/planner/rules/parallel/rule-async-gather-union-all.js +9 -1
- package/dist/src/planner/rules/parallel/rule-async-gather-union-all.js.map +1 -1
- package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.d.ts.map +1 -1
- package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js +7 -0
- package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js.map +1 -1
- package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.d.ts.map +1 -1
- package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.js +10 -1
- package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.js +10 -1
- package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-empty-relation-folding.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js +18 -0
- package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-filter-contradiction.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-filter-contradiction.js +7 -0
- package/dist/src/planner/rules/predicate/rule-filter-contradiction.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js +9 -0
- package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js +13 -3
- package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +2 -2
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-projection-pruning.d.ts.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-projection-pruning.js +14 -0
- package/dist/src/planner/rules/retrieve/rule-projection-pruning.js.map +1 -1
- package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.d.ts +16 -0
- package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.d.ts.map +1 -1
- package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.js +47 -4
- package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.js.map +1 -1
- package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.d.ts.map +1 -1
- package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js +8 -0
- package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js.map +1 -1
- package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.d.ts.map +1 -1
- package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js +7 -0
- package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js.map +1 -1
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +12 -0
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
- package/dist/src/planner/rules/window/rule-monotonic-window.js +1 -1
- package/dist/src/planner/rules/window/rule-monotonic-window.js.map +1 -1
- package/dist/src/planner/type-utils.d.ts +14 -0
- package/dist/src/planner/type-utils.d.ts.map +1 -1
- package/dist/src/planner/type-utils.js +66 -21
- package/dist/src/planner/type-utils.js.map +1 -1
- package/dist/src/planner/util/fd-utils.d.ts +228 -36
- package/dist/src/planner/util/fd-utils.d.ts.map +1 -1
- package/dist/src/planner/util/fd-utils.js +501 -84
- package/dist/src/planner/util/fd-utils.js.map +1 -1
- package/dist/src/planner/util/ind-utils.d.ts +27 -1
- package/dist/src/planner/util/ind-utils.d.ts.map +1 -1
- package/dist/src/planner/util/ind-utils.js +80 -6
- package/dist/src/planner/util/ind-utils.js.map +1 -1
- package/dist/src/planner/util/key-utils.d.ts +26 -3
- package/dist/src/planner/util/key-utils.d.ts.map +1 -1
- package/dist/src/planner/util/key-utils.js +182 -33
- package/dist/src/planner/util/key-utils.js.map +1 -1
- package/dist/src/planner/util/set-op-wrapper.d.ts +37 -0
- package/dist/src/planner/util/set-op-wrapper.d.ts.map +1 -0
- package/dist/src/planner/util/set-op-wrapper.js +82 -0
- package/dist/src/planner/util/set-op-wrapper.js.map +1 -0
- package/dist/src/planner/validation/plan-validator.d.ts.map +1 -1
- package/dist/src/planner/validation/plan-validator.js +1 -0
- package/dist/src/planner/validation/plan-validator.js.map +1 -1
- package/dist/src/runtime/context-helpers.d.ts +13 -1
- package/dist/src/runtime/context-helpers.d.ts.map +1 -1
- package/dist/src/runtime/context-helpers.js +7 -1
- package/dist/src/runtime/context-helpers.js.map +1 -1
- package/dist/src/runtime/delta-executor.d.ts +30 -1
- package/dist/src/runtime/delta-executor.d.ts.map +1 -1
- package/dist/src/runtime/delta-executor.js +38 -4
- package/dist/src/runtime/delta-executor.js.map +1 -1
- package/dist/src/runtime/emit/add-constraint.d.ts.map +1 -1
- package/dist/src/runtime/emit/add-constraint.js +38 -5
- package/dist/src/runtime/emit/add-constraint.js.map +1 -1
- package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/aggregate.js +10 -8
- package/dist/src/runtime/emit/aggregate.js.map +1 -1
- package/dist/src/runtime/emit/alter-table.d.ts +1 -1
- package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/alter-table.js +664 -108
- package/dist/src/runtime/emit/alter-table.js.map +1 -1
- package/dist/src/runtime/emit/analyze.d.ts.map +1 -1
- package/dist/src/runtime/emit/analyze.js +2 -1
- package/dist/src/runtime/emit/analyze.js.map +1 -1
- package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/asof-scan.js +24 -9
- package/dist/src/runtime/emit/asof-scan.js.map +1 -1
- package/dist/src/runtime/emit/asserted-keys.d.ts +13 -0
- package/dist/src/runtime/emit/asserted-keys.d.ts.map +1 -0
- package/dist/src/runtime/emit/asserted-keys.js +13 -0
- package/dist/src/runtime/emit/asserted-keys.js.map +1 -0
- package/dist/src/runtime/emit/between.d.ts.map +1 -1
- package/dist/src/runtime/emit/between.js +24 -19
- package/dist/src/runtime/emit/between.js.map +1 -1
- package/dist/src/runtime/emit/binary.d.ts.map +1 -1
- package/dist/src/runtime/emit/binary.js +24 -36
- package/dist/src/runtime/emit/binary.js.map +1 -1
- package/dist/src/runtime/emit/block.d.ts.map +1 -1
- package/dist/src/runtime/emit/block.js +11 -2
- package/dist/src/runtime/emit/block.js.map +1 -1
- package/dist/src/runtime/emit/bloom-join.d.ts.map +1 -1
- package/dist/src/runtime/emit/bloom-join.js +12 -4
- package/dist/src/runtime/emit/bloom-join.js.map +1 -1
- package/dist/src/runtime/emit/constraint-check.d.ts.map +1 -1
- package/dist/src/runtime/emit/constraint-check.js +50 -1
- package/dist/src/runtime/emit/constraint-check.js.map +1 -1
- package/dist/src/runtime/emit/create-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/create-table.js +8 -0
- package/dist/src/runtime/emit/create-table.js.map +1 -1
- package/dist/src/runtime/emit/create-view.d.ts.map +1 -1
- package/dist/src/runtime/emit/create-view.js +16 -1
- package/dist/src/runtime/emit/create-view.js.map +1 -1
- package/dist/src/runtime/emit/delete.d.ts.map +1 -1
- package/dist/src/runtime/emit/delete.js +15 -5
- package/dist/src/runtime/emit/delete.js.map +1 -1
- package/dist/src/runtime/emit/dml-executor.d.ts +27 -0
- package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
- package/dist/src/runtime/emit/dml-executor.js +413 -193
- package/dist/src/runtime/emit/dml-executor.js.map +1 -1
- package/dist/src/runtime/emit/drop-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/drop-table.js +10 -0
- package/dist/src/runtime/emit/drop-table.js.map +1 -1
- package/dist/src/runtime/emit/drop-view.d.ts.map +1 -1
- package/dist/src/runtime/emit/drop-view.js +17 -0
- package/dist/src/runtime/emit/drop-view.js.map +1 -1
- package/dist/src/runtime/emit/envelope-scan.d.ts +13 -0
- package/dist/src/runtime/emit/envelope-scan.d.ts.map +1 -0
- package/dist/src/runtime/emit/envelope-scan.js +22 -0
- package/dist/src/runtime/emit/envelope-scan.js.map +1 -0
- package/dist/src/runtime/emit/join.d.ts +10 -2
- package/dist/src/runtime/emit/join.d.ts.map +1 -1
- package/dist/src/runtime/emit/join.js +128 -38
- package/dist/src/runtime/emit/join.js.map +1 -1
- package/dist/src/runtime/emit/lens-auxiliary-access.d.ts +16 -0
- package/dist/src/runtime/emit/lens-auxiliary-access.d.ts.map +1 -0
- package/dist/src/runtime/emit/lens-auxiliary-access.js +16 -0
- package/dist/src/runtime/emit/lens-auxiliary-access.js.map +1 -0
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts +640 -0
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -0
- package/dist/src/runtime/emit/materialized-view-helpers.js +2576 -0
- package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -0
- package/dist/src/runtime/emit/materialized-view.d.ts +31 -0
- package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -0
- package/dist/src/runtime/emit/materialized-view.js +187 -0
- package/dist/src/runtime/emit/materialized-view.js.map +1 -0
- package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
- package/dist/src/runtime/emit/merge-join.js +19 -5
- package/dist/src/runtime/emit/merge-join.js.map +1 -1
- package/dist/src/runtime/emit/project.d.ts.map +1 -1
- package/dist/src/runtime/emit/project.js +10 -5
- package/dist/src/runtime/emit/project.js.map +1 -1
- package/dist/src/runtime/emit/schema-declarative.d.ts +1 -0
- package/dist/src/runtime/emit/schema-declarative.d.ts.map +1 -1
- package/dist/src/runtime/emit/schema-declarative.js +101 -5
- package/dist/src/runtime/emit/schema-declarative.js.map +1 -1
- package/dist/src/runtime/emit/set-object-tags.d.ts +16 -0
- package/dist/src/runtime/emit/set-object-tags.d.ts.map +1 -0
- package/dist/src/runtime/emit/set-object-tags.js +57 -0
- package/dist/src/runtime/emit/set-object-tags.js.map +1 -0
- package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
- package/dist/src/runtime/emit/set-operation.js +140 -24
- package/dist/src/runtime/emit/set-operation.js.map +1 -1
- package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
- package/dist/src/runtime/emit/subquery.js +110 -5
- package/dist/src/runtime/emit/subquery.js.map +1 -1
- package/dist/src/runtime/emit/unary.d.ts.map +1 -1
- package/dist/src/runtime/emit/unary.js +34 -6
- package/dist/src/runtime/emit/unary.js.map +1 -1
- package/dist/src/runtime/emit/view-mutation.d.ts +70 -0
- package/dist/src/runtime/emit/view-mutation.d.ts.map +1 -0
- package/dist/src/runtime/emit/view-mutation.js +299 -0
- package/dist/src/runtime/emit/view-mutation.js.map +1 -0
- package/dist/src/runtime/emit/window.js +29 -5
- package/dist/src/runtime/emit/window.js.map +1 -1
- package/dist/src/runtime/foreign-key-actions.d.ts +66 -3
- package/dist/src/runtime/foreign-key-actions.d.ts.map +1 -1
- package/dist/src/runtime/foreign-key-actions.js +580 -172
- package/dist/src/runtime/foreign-key-actions.js.map +1 -1
- package/dist/src/runtime/parallel-driver.d.ts +4 -1
- package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
- package/dist/src/runtime/parallel-driver.js +5 -1
- package/dist/src/runtime/parallel-driver.js.map +1 -1
- package/dist/src/runtime/register.d.ts.map +1 -1
- package/dist/src/runtime/register.js +17 -1
- package/dist/src/runtime/register.js.map +1 -1
- package/dist/src/runtime/types.d.ts +10 -0
- package/dist/src/runtime/types.d.ts.map +1 -1
- package/dist/src/runtime/types.js.map +1 -1
- package/dist/src/schema/basis-backfill.d.ts +63 -0
- package/dist/src/schema/basis-backfill.d.ts.map +1 -0
- package/dist/src/schema/basis-backfill.js +161 -0
- package/dist/src/schema/basis-backfill.js.map +1 -0
- package/dist/src/schema/catalog.d.ts +115 -1
- package/dist/src/schema/catalog.d.ts.map +1 -1
- package/dist/src/schema/catalog.js +249 -22
- package/dist/src/schema/catalog.js.map +1 -1
- package/dist/src/schema/change-events.d.ts +42 -1
- package/dist/src/schema/change-events.d.ts.map +1 -1
- package/dist/src/schema/change-events.js.map +1 -1
- package/dist/src/schema/column.d.ts +16 -0
- package/dist/src/schema/column.d.ts.map +1 -1
- package/dist/src/schema/column.js.map +1 -1
- package/dist/src/schema/constraint-builder.d.ts +182 -0
- package/dist/src/schema/constraint-builder.d.ts.map +1 -0
- package/dist/src/schema/constraint-builder.js +424 -0
- package/dist/src/schema/constraint-builder.js.map +1 -0
- package/dist/src/schema/ddl-generator.d.ts +86 -1
- package/dist/src/schema/ddl-generator.d.ts.map +1 -1
- package/dist/src/schema/ddl-generator.js +316 -20
- package/dist/src/schema/ddl-generator.js.map +1 -1
- package/dist/src/schema/declared-schema-manager.d.ts +51 -0
- package/dist/src/schema/declared-schema-manager.d.ts.map +1 -1
- package/dist/src/schema/declared-schema-manager.js +61 -0
- package/dist/src/schema/declared-schema-manager.js.map +1 -1
- package/dist/src/schema/derivation.d.ts +106 -0
- package/dist/src/schema/derivation.d.ts.map +1 -0
- package/dist/src/schema/derivation.js +25 -0
- package/dist/src/schema/derivation.js.map +1 -0
- package/dist/src/schema/function.d.ts +13 -0
- package/dist/src/schema/function.d.ts.map +1 -1
- package/dist/src/schema/function.js.map +1 -1
- package/dist/src/schema/lens-ack.d.ts +90 -0
- package/dist/src/schema/lens-ack.d.ts.map +1 -0
- package/dist/src/schema/lens-ack.js +361 -0
- package/dist/src/schema/lens-ack.js.map +1 -0
- package/dist/src/schema/lens-compiler.d.ts +62 -0
- package/dist/src/schema/lens-compiler.d.ts.map +1 -0
- package/dist/src/schema/lens-compiler.js +1594 -0
- package/dist/src/schema/lens-compiler.js.map +1 -0
- package/dist/src/schema/lens-fk-discovery.d.ts +175 -0
- package/dist/src/schema/lens-fk-discovery.d.ts.map +1 -0
- package/dist/src/schema/lens-fk-discovery.js +336 -0
- package/dist/src/schema/lens-fk-discovery.js.map +1 -0
- package/dist/src/schema/lens-prover.d.ts +336 -0
- package/dist/src/schema/lens-prover.d.ts.map +1 -0
- package/dist/src/schema/lens-prover.js +1988 -0
- package/dist/src/schema/lens-prover.js.map +1 -0
- package/dist/src/schema/lens.d.ts +254 -0
- package/dist/src/schema/lens.d.ts.map +1 -0
- package/dist/src/schema/lens.js +21 -0
- package/dist/src/schema/lens.js.map +1 -0
- package/dist/src/schema/manager.d.ts +676 -18
- package/dist/src/schema/manager.d.ts.map +1 -1
- package/dist/src/schema/manager.js +1573 -238
- package/dist/src/schema/manager.js.map +1 -1
- package/dist/src/schema/mapping-advertisement-tags.d.ts +39 -0
- package/dist/src/schema/mapping-advertisement-tags.d.ts.map +1 -0
- package/dist/src/schema/mapping-advertisement-tags.js +216 -0
- package/dist/src/schema/mapping-advertisement-tags.js.map +1 -0
- package/dist/src/schema/rename-rewriter.d.ts +45 -4
- package/dist/src/schema/rename-rewriter.d.ts.map +1 -1
- package/dist/src/schema/rename-rewriter.js +412 -19
- package/dist/src/schema/rename-rewriter.js.map +1 -1
- package/dist/src/schema/reserved-tags-policy.d.ts +32 -0
- package/dist/src/schema/reserved-tags-policy.d.ts.map +1 -0
- package/dist/src/schema/reserved-tags-policy.js +34 -0
- package/dist/src/schema/reserved-tags-policy.js.map +1 -0
- package/dist/src/schema/reserved-tags.d.ts +170 -0
- package/dist/src/schema/reserved-tags.d.ts.map +1 -0
- package/dist/src/schema/reserved-tags.js +507 -0
- package/dist/src/schema/reserved-tags.js.map +1 -0
- package/dist/src/schema/schema-differ.d.ts +158 -2
- package/dist/src/schema/schema-differ.d.ts.map +1 -1
- package/dist/src/schema/schema-differ.js +1460 -78
- package/dist/src/schema/schema-differ.js.map +1 -1
- package/dist/src/schema/schema-hasher.d.ts +8 -3
- package/dist/src/schema/schema-hasher.d.ts.map +1 -1
- package/dist/src/schema/schema-hasher.js +22 -2
- package/dist/src/schema/schema-hasher.js.map +1 -1
- package/dist/src/schema/schema.d.ts +25 -1
- package/dist/src/schema/schema.d.ts.map +1 -1
- package/dist/src/schema/schema.js +36 -2
- package/dist/src/schema/schema.js.map +1 -1
- package/dist/src/schema/table.d.ts +259 -10
- package/dist/src/schema/table.d.ts.map +1 -1
- package/dist/src/schema/table.js +309 -26
- package/dist/src/schema/table.js.map +1 -1
- package/dist/src/schema/unique-enforcement.d.ts +78 -0
- package/dist/src/schema/unique-enforcement.d.ts.map +1 -0
- package/dist/src/schema/unique-enforcement.js +93 -0
- package/dist/src/schema/unique-enforcement.js.map +1 -0
- package/dist/src/schema/view.d.ts +83 -2
- package/dist/src/schema/view.d.ts.map +1 -1
- package/dist/src/schema/view.js +67 -1
- package/dist/src/schema/view.js.map +1 -1
- package/dist/src/schema/window-function.d.ts +9 -1
- package/dist/src/schema/window-function.d.ts.map +1 -1
- package/dist/src/schema/window-function.js.map +1 -1
- package/dist/src/types/temporal-types.d.ts.map +1 -1
- package/dist/src/types/temporal-types.js +71 -36
- package/dist/src/types/temporal-types.js.map +1 -1
- package/dist/src/util/comparison.d.ts +24 -0
- package/dist/src/util/comparison.d.ts.map +1 -1
- package/dist/src/util/comparison.js +34 -0
- package/dist/src/util/comparison.js.map +1 -1
- package/dist/src/util/mutation-statement.d.ts.map +1 -1
- package/dist/src/util/mutation-statement.js +4 -1
- package/dist/src/util/mutation-statement.js.map +1 -1
- package/dist/src/util/serialization.d.ts +9 -0
- package/dist/src/util/serialization.d.ts.map +1 -1
- package/dist/src/util/serialization.js +26 -0
- package/dist/src/util/serialization.js.map +1 -1
- package/dist/src/vtab/backing-host.d.ts +286 -0
- package/dist/src/vtab/backing-host.d.ts.map +1 -0
- package/dist/src/vtab/backing-host.js +118 -0
- package/dist/src/vtab/backing-host.js.map +1 -0
- package/dist/src/vtab/best-access-plan.d.ts +21 -0
- package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
- package/dist/src/vtab/best-access-plan.js.map +1 -1
- package/dist/src/vtab/capabilities.d.ts +5 -5
- package/dist/src/vtab/capabilities.d.ts.map +1 -1
- package/dist/src/vtab/mapping-advertisement.d.ts +163 -0
- package/dist/src/vtab/mapping-advertisement.d.ts.map +1 -0
- package/dist/src/vtab/mapping-advertisement.js +2 -0
- package/dist/src/vtab/mapping-advertisement.js.map +1 -0
- package/dist/src/vtab/memory/index.d.ts +64 -4
- package/dist/src/vtab/memory/index.d.ts.map +1 -1
- package/dist/src/vtab/memory/index.js +119 -12
- package/dist/src/vtab/memory/index.js.map +1 -1
- package/dist/src/vtab/memory/layer/base.d.ts +38 -1
- package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/base.js +112 -24
- package/dist/src/vtab/memory/layer/base.js.map +1 -1
- package/dist/src/vtab/memory/layer/manager.d.ts +291 -4
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +1050 -91
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/dist/src/vtab/memory/layer/plan-filter.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/plan-filter.js +35 -6
- package/dist/src/vtab/memory/layer/plan-filter.js.map +1 -1
- package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/scan-layer.js +66 -14
- package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
- package/dist/src/vtab/memory/layer/scan-plan.d.ts +14 -0
- package/dist/src/vtab/memory/layer/scan-plan.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/scan-plan.js +27 -4
- package/dist/src/vtab/memory/layer/scan-plan.js.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.js +5 -1
- package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
- package/dist/src/vtab/memory/module.d.ts +17 -0
- package/dist/src/vtab/memory/module.d.ts.map +1 -1
- package/dist/src/vtab/memory/module.js +82 -3
- package/dist/src/vtab/memory/module.js.map +1 -1
- package/dist/src/vtab/memory/table.d.ts.map +1 -1
- package/dist/src/vtab/memory/table.js +15 -5
- package/dist/src/vtab/memory/table.js.map +1 -1
- package/dist/src/vtab/memory/types.d.ts +20 -2
- package/dist/src/vtab/memory/types.d.ts.map +1 -1
- package/dist/src/vtab/memory/utils/predicate.d.ts.map +1 -1
- package/dist/src/vtab/memory/utils/predicate.js +46 -24
- package/dist/src/vtab/memory/utils/predicate.js.map +1 -1
- package/dist/src/vtab/memory/utils/primary-key-encode.d.ts +31 -0
- package/dist/src/vtab/memory/utils/primary-key-encode.d.ts.map +1 -0
- package/dist/src/vtab/memory/utils/primary-key-encode.js +101 -0
- package/dist/src/vtab/memory/utils/primary-key-encode.js.map +1 -0
- package/dist/src/vtab/memory/utils/primary-key.d.ts +8 -0
- package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
- package/dist/src/vtab/memory/utils/primary-key.js +12 -5
- package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
- package/dist/src/vtab/module.d.ts +203 -4
- package/dist/src/vtab/module.d.ts.map +1 -1
- package/dist/src/vtab/table.d.ts +9 -0
- package/dist/src/vtab/table.d.ts.map +1 -1
- package/dist/src/vtab/table.js.map +1 -1
- package/package.json +6 -5
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLogger } from '../common/logger.js'; // Import logger
|
|
2
|
-
import { Lexer, TokenType } from './lexer.js';
|
|
2
|
+
import { Lexer, TokenType, CONTEXTUAL_KEYWORDS } from './lexer.js';
|
|
3
3
|
import { ConflictResolution } from '../common/constants.js';
|
|
4
4
|
import { quereusError, QuereusError } from '../common/errors.js';
|
|
5
5
|
import { StatusCode } from '../common/types.js';
|
|
@@ -32,8 +32,8 @@ function _createLoc(startToken, endToken) {
|
|
|
32
32
|
/**
|
|
33
33
|
* IMPORTANT: Any changes to parsed syntax must also be reflected in the corresponding emitters:
|
|
34
34
|
* - packages/quereus/src/emit/ast-stringify.ts (AST-to-SQL string conversion)
|
|
35
|
-
* - packages/quereus/src/schema/catalog.ts (DDL
|
|
36
|
-
* - packages/quereus
|
|
35
|
+
* - packages/quereus/src/schema/catalog.ts (CREATE ASSERTION DDL for catalog/hashing)
|
|
36
|
+
* - packages/quereus/src/schema/ddl-generator.ts (canonical DDL generation for persistence)
|
|
37
37
|
* If only the parser is updated, SQL round-trips and persisted schemas will silently lose the new syntax.
|
|
38
38
|
*/
|
|
39
39
|
export class Parser {
|
|
@@ -168,20 +168,108 @@ export class Parser {
|
|
|
168
168
|
const endToken = this.previous(); // Last token of the WITH clause
|
|
169
169
|
return { type: 'with', recursive, ctes, options, loc: _createLoc(startToken, endToken) };
|
|
170
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Parses a relation-producing query expression (`QueryExpr`):
|
|
173
|
+
* `[WITH …] (SELECT | VALUES | INSERT|UPDATE|DELETE)`.
|
|
174
|
+
*
|
|
175
|
+
* `outerWithContext` is an outer WITH already consumed by the caller and
|
|
176
|
+
* forwarded into inner statements purely for CTE-resolution context (the
|
|
177
|
+
* planner reads CTE definitions out of `select.withClause`). It is NOT
|
|
178
|
+
* stored on the returned node — that already happened at the outer site.
|
|
179
|
+
*
|
|
180
|
+
* If the body itself leads with `WITH`, that inner WITH is consumed here
|
|
181
|
+
* and attached to the resulting statement. The two WITH clauses do not
|
|
182
|
+
* mix: an inner body-level WITH wins.
|
|
183
|
+
*
|
|
184
|
+
* `requireReturning` enforces the rule that DML used in non-top-level
|
|
185
|
+
* relation positions (FROM subquery, scalar / IN / EXISTS subquery,
|
|
186
|
+
* compound leg, CTE body, view body) must carry a RETURNING clause —
|
|
187
|
+
* the outer position consumes a relation, not a side-effect.
|
|
188
|
+
*/
|
|
189
|
+
parseQueryExpr(outerWithContext, requireReturning = false) {
|
|
190
|
+
// Inner body-level WITH (e.g. `(WITH t AS (…) SELECT … FROM t)`). The
|
|
191
|
+
// inner WITH is owned by the produced statement; we still pass it down
|
|
192
|
+
// to the inner builder for the same resolution-context reason and
|
|
193
|
+
// re-attach explicitly so callers that swap it out (rename rewriter,
|
|
194
|
+
// declared-schema canonicaliser) see a consistent shape.
|
|
195
|
+
let innerWith;
|
|
196
|
+
if (this.check(TokenType.WITH)) {
|
|
197
|
+
innerWith = this.tryParseWithClause();
|
|
198
|
+
}
|
|
199
|
+
const resolutionContext = innerWith ?? outerWithContext;
|
|
200
|
+
const startToken = this.peek();
|
|
201
|
+
let stmt;
|
|
202
|
+
if (this.check(TokenType.LPAREN)) {
|
|
203
|
+
// A parenthesized query expression `( <query-expr> )` is a valid first
|
|
204
|
+
// operand (view body / CTE body / FROM-subquery / scalar subquery all
|
|
205
|
+
// funnel here). Read it, then run the shared left-associative tail so
|
|
206
|
+
// `(…) union …` chains. Pure grouping (no compound follows) returns the
|
|
207
|
+
// inner expression unchanged — `(select 1)` ≡ `select 1`.
|
|
208
|
+
const firstOperand = this.parseCompoundOperand(resolutionContext, requireReturning);
|
|
209
|
+
stmt = this.checkCompoundOperator()
|
|
210
|
+
? this.parseCompoundTail(firstOperand, resolutionContext, startToken)
|
|
211
|
+
: firstOperand;
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const kw = startToken.lexeme.toUpperCase();
|
|
215
|
+
switch (kw) {
|
|
216
|
+
case 'SELECT':
|
|
217
|
+
this.advance();
|
|
218
|
+
stmt = this.selectStatement(startToken, resolutionContext);
|
|
219
|
+
break;
|
|
220
|
+
case 'VALUES':
|
|
221
|
+
this.advance();
|
|
222
|
+
stmt = this.valuesStatementWithOptionalCompound(startToken, resolutionContext);
|
|
223
|
+
break;
|
|
224
|
+
case 'INSERT':
|
|
225
|
+
this.advance();
|
|
226
|
+
stmt = this.insertStatement(startToken, resolutionContext);
|
|
227
|
+
break;
|
|
228
|
+
case 'UPDATE':
|
|
229
|
+
this.advance();
|
|
230
|
+
stmt = this.updateStatement(startToken, resolutionContext);
|
|
231
|
+
break;
|
|
232
|
+
case 'DELETE':
|
|
233
|
+
this.advance();
|
|
234
|
+
stmt = this.deleteStatement(startToken, resolutionContext);
|
|
235
|
+
break;
|
|
236
|
+
default:
|
|
237
|
+
throw this.error(startToken, "Expected SELECT, VALUES, INSERT, UPDATE, or DELETE in query expression.");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (innerWith) {
|
|
241
|
+
if (this.statementSupportsWithClause(stmt)) {
|
|
242
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
243
|
+
stmt.withClause = innerWith;
|
|
244
|
+
if (innerWith.loc && stmt.loc) {
|
|
245
|
+
stmt.loc.start = innerWith.loc.start;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
throw this.error(this.previous(), `WITH clause cannot be used with ${stmt.type} statement.`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (requireReturning && (stmt.type === 'insert' || stmt.type === 'update' || stmt.type === 'delete')) {
|
|
253
|
+
if (!stmt.returning || stmt.returning.length === 0) {
|
|
254
|
+
throw this.error(this.previous(), `${stmt.type.toUpperCase()} in a relation position must have a RETURNING clause.`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return stmt;
|
|
258
|
+
}
|
|
171
259
|
/**
|
|
172
260
|
* Parses a single Common Table Expression (CTE).
|
|
173
261
|
* cte_name [(col1, col2, ...)] AS (query)
|
|
174
262
|
*/
|
|
175
263
|
commonTableExpression() {
|
|
176
264
|
const startToken = this.peek(); // Peek before consuming name
|
|
177
|
-
const name = this.consumeIdentifier(
|
|
265
|
+
const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected CTE name.");
|
|
178
266
|
let endToken = this.previous(); // End token initially is the name
|
|
179
267
|
let columns;
|
|
180
268
|
if (this.match(TokenType.LPAREN)) {
|
|
181
269
|
columns = [];
|
|
182
270
|
if (!this.check(TokenType.RPAREN)) {
|
|
183
271
|
do {
|
|
184
|
-
columns.push(this.consumeIdentifier(
|
|
272
|
+
columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in CTE definition."));
|
|
185
273
|
} while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
|
|
186
274
|
}
|
|
187
275
|
endToken = this.consume(TokenType.RPAREN, "Expected ')' after CTE column list.");
|
|
@@ -196,29 +284,8 @@ export class Parser {
|
|
|
196
284
|
materializationHint = 'not_materialized';
|
|
197
285
|
}
|
|
198
286
|
this.consume(TokenType.LPAREN, "Expected '(' before CTE query.");
|
|
199
|
-
//
|
|
200
|
-
const
|
|
201
|
-
let query;
|
|
202
|
-
if (this.check(TokenType.SELECT)) {
|
|
203
|
-
this.advance(); // Consume SELECT token
|
|
204
|
-
query = this.selectStatement(queryStartToken); // Pass start token
|
|
205
|
-
}
|
|
206
|
-
else if (this.check(TokenType.INSERT)) {
|
|
207
|
-
this.advance(); // Consume INSERT token
|
|
208
|
-
query = this.insertStatement(queryStartToken);
|
|
209
|
-
}
|
|
210
|
-
else if (this.check(TokenType.UPDATE)) {
|
|
211
|
-
this.advance(); // Consume UPDATE token
|
|
212
|
-
query = this.updateStatement(queryStartToken);
|
|
213
|
-
}
|
|
214
|
-
else if (this.check(TokenType.DELETE)) {
|
|
215
|
-
this.advance(); // Consume DELETE token
|
|
216
|
-
query = this.deleteStatement(queryStartToken);
|
|
217
|
-
}
|
|
218
|
-
// TODO: Add support for VALUES directly if needed (though VALUES is usually part of SELECT)
|
|
219
|
-
else {
|
|
220
|
-
throw this.error(this.peek(), "Expected SELECT, INSERT, UPDATE, or DELETE statement for CTE query.");
|
|
221
|
-
}
|
|
287
|
+
// CTE body is any QueryExpr; DML bodies must carry RETURNING.
|
|
288
|
+
const query = this.parseQueryExpr(undefined, /*requireReturning*/ true);
|
|
222
289
|
endToken = this.consume(TokenType.RPAREN, "Expected ')' after CTE query."); // Capture ')' as end token
|
|
223
290
|
return { type: 'commonTableExpr', name, columns, query, materializationHint, loc: _createLoc(startToken, endToken) };
|
|
224
291
|
}
|
|
@@ -235,89 +302,106 @@ export class Parser {
|
|
|
235
302
|
// --- Check for specific keywords first ---
|
|
236
303
|
const currentKeyword = startToken.lexeme.toUpperCase();
|
|
237
304
|
let stmt;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
this.advance();
|
|
245
|
-
stmt = this.insertStatement(startToken, withClause);
|
|
246
|
-
break;
|
|
247
|
-
case 'UPDATE':
|
|
248
|
-
this.advance();
|
|
249
|
-
stmt = this.updateStatement(startToken, withClause);
|
|
250
|
-
break;
|
|
251
|
-
case 'DELETE':
|
|
252
|
-
this.advance();
|
|
253
|
-
stmt = this.deleteStatement(startToken, withClause);
|
|
254
|
-
break;
|
|
255
|
-
case 'VALUES':
|
|
256
|
-
this.advance();
|
|
257
|
-
stmt = this.valuesStatement(startToken);
|
|
258
|
-
break;
|
|
259
|
-
case 'CREATE':
|
|
260
|
-
this.advance();
|
|
261
|
-
stmt = this.createStatement(startToken, withClause);
|
|
262
|
-
break;
|
|
263
|
-
case 'DROP':
|
|
264
|
-
this.advance();
|
|
265
|
-
stmt = this.dropStatement(startToken, withClause);
|
|
266
|
-
break;
|
|
267
|
-
case 'ALTER':
|
|
268
|
-
this.advance();
|
|
269
|
-
stmt = this.alterTableStatement(startToken, withClause);
|
|
270
|
-
break;
|
|
271
|
-
case 'BEGIN':
|
|
272
|
-
this.advance();
|
|
273
|
-
stmt = this.beginStatement(startToken, withClause);
|
|
274
|
-
break;
|
|
275
|
-
case 'COMMIT':
|
|
276
|
-
this.advance();
|
|
277
|
-
stmt = this.commitStatement(startToken, withClause);
|
|
278
|
-
break;
|
|
279
|
-
case 'ROLLBACK':
|
|
280
|
-
this.advance();
|
|
281
|
-
stmt = this.rollbackStatement(startToken, withClause);
|
|
282
|
-
break;
|
|
283
|
-
case 'SAVEPOINT':
|
|
284
|
-
this.advance();
|
|
285
|
-
stmt = this.savepointStatement(startToken, withClause);
|
|
286
|
-
break;
|
|
287
|
-
case 'RELEASE':
|
|
288
|
-
this.advance();
|
|
289
|
-
stmt = this.releaseStatement(startToken, withClause);
|
|
290
|
-
break;
|
|
291
|
-
// TODO: Replace pragmas with build-in functions
|
|
292
|
-
case 'PRAGMA':
|
|
293
|
-
this.advance();
|
|
294
|
-
stmt = this.pragmaStatement(startToken, withClause);
|
|
295
|
-
break;
|
|
296
|
-
case 'ANALYZE':
|
|
297
|
-
this.advance();
|
|
298
|
-
stmt = this.analyzeStatement(startToken);
|
|
299
|
-
break;
|
|
300
|
-
case 'DECLARE':
|
|
301
|
-
this.advance();
|
|
302
|
-
stmt = this.declareSchemaStatement(startToken);
|
|
303
|
-
break;
|
|
304
|
-
case 'DIFF':
|
|
305
|
-
this.advance();
|
|
306
|
-
stmt = this.diffSchemaStatement(startToken);
|
|
307
|
-
break;
|
|
308
|
-
case 'APPLY':
|
|
309
|
-
this.advance();
|
|
310
|
-
stmt = this.applySchemaStatement(startToken);
|
|
311
|
-
break;
|
|
312
|
-
case 'EXPLAIN':
|
|
313
|
-
this.advance();
|
|
314
|
-
stmt = this.explainSchemaStatement(startToken);
|
|
315
|
-
break;
|
|
316
|
-
// --- Add default case ---
|
|
317
|
-
default:
|
|
318
|
-
// If it wasn't a recognized keyword starting the statement
|
|
319
|
-
throw this.error(startToken, `Expected statement type (SELECT, INSERT, UPDATE, DELETE, VALUES, CREATE, etc.), got '${startToken.lexeme}'.`);
|
|
305
|
+
// A leading `(` is a parenthesized query expression at top level
|
|
306
|
+
// (`(select 1) union (select 2);` — SQLite parity). `parseQueryExpr`
|
|
307
|
+
// reads the operand and any compound tail; the post-switch WITH-attach
|
|
308
|
+
// below still folds an outer `with … (select …) union …` in.
|
|
309
|
+
if (this.check(TokenType.LPAREN)) {
|
|
310
|
+
stmt = this.parseQueryExpr(withClause);
|
|
320
311
|
}
|
|
312
|
+
else
|
|
313
|
+
switch (currentKeyword) {
|
|
314
|
+
case 'SELECT':
|
|
315
|
+
this.advance();
|
|
316
|
+
stmt = this.selectStatement(startToken, withClause);
|
|
317
|
+
break;
|
|
318
|
+
case 'INSERT':
|
|
319
|
+
this.advance();
|
|
320
|
+
stmt = this.insertStatement(startToken, withClause);
|
|
321
|
+
break;
|
|
322
|
+
case 'UPDATE':
|
|
323
|
+
this.advance();
|
|
324
|
+
stmt = this.updateStatement(startToken, withClause);
|
|
325
|
+
break;
|
|
326
|
+
case 'DELETE':
|
|
327
|
+
this.advance();
|
|
328
|
+
stmt = this.deleteStatement(startToken, withClause);
|
|
329
|
+
break;
|
|
330
|
+
case 'VALUES':
|
|
331
|
+
this.advance();
|
|
332
|
+
stmt = this.valuesStatementWithOptionalCompound(startToken, withClause);
|
|
333
|
+
break;
|
|
334
|
+
case 'CREATE':
|
|
335
|
+
this.advance();
|
|
336
|
+
stmt = this.createStatement(startToken, withClause);
|
|
337
|
+
break;
|
|
338
|
+
case 'REFRESH':
|
|
339
|
+
this.advance();
|
|
340
|
+
stmt = this.refreshStatement(startToken, withClause);
|
|
341
|
+
break;
|
|
342
|
+
case 'DROP':
|
|
343
|
+
this.advance();
|
|
344
|
+
stmt = this.dropStatement(startToken, withClause);
|
|
345
|
+
break;
|
|
346
|
+
case 'ALTER':
|
|
347
|
+
this.advance();
|
|
348
|
+
stmt = this.alterStatement(startToken, withClause);
|
|
349
|
+
break;
|
|
350
|
+
case 'BEGIN':
|
|
351
|
+
this.advance();
|
|
352
|
+
stmt = this.beginStatement(startToken, withClause);
|
|
353
|
+
break;
|
|
354
|
+
case 'COMMIT':
|
|
355
|
+
this.advance();
|
|
356
|
+
stmt = this.commitStatement(startToken, withClause);
|
|
357
|
+
break;
|
|
358
|
+
case 'ROLLBACK':
|
|
359
|
+
this.advance();
|
|
360
|
+
stmt = this.rollbackStatement(startToken, withClause);
|
|
361
|
+
break;
|
|
362
|
+
case 'SAVEPOINT':
|
|
363
|
+
this.advance();
|
|
364
|
+
stmt = this.savepointStatement(startToken, withClause);
|
|
365
|
+
break;
|
|
366
|
+
case 'RELEASE':
|
|
367
|
+
this.advance();
|
|
368
|
+
stmt = this.releaseStatement(startToken, withClause);
|
|
369
|
+
break;
|
|
370
|
+
// TODO: Replace pragmas with build-in functions
|
|
371
|
+
case 'PRAGMA':
|
|
372
|
+
this.advance();
|
|
373
|
+
stmt = this.pragmaStatement(startToken, withClause);
|
|
374
|
+
break;
|
|
375
|
+
case 'ANALYZE':
|
|
376
|
+
this.advance();
|
|
377
|
+
stmt = this.analyzeStatement(startToken);
|
|
378
|
+
break;
|
|
379
|
+
case 'DECLARE': {
|
|
380
|
+
this.advance();
|
|
381
|
+
// `declare lens …` is a sibling statement, not a `declare schema`
|
|
382
|
+
// variant: branch on the contextual LENS keyword.
|
|
383
|
+
stmt = this.peekKeyword('LENS')
|
|
384
|
+
? this.declareLensStatement(startToken)
|
|
385
|
+
: this.declareSchemaStatement(startToken);
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
case 'DIFF':
|
|
389
|
+
this.advance();
|
|
390
|
+
stmt = this.diffSchemaStatement(startToken);
|
|
391
|
+
break;
|
|
392
|
+
case 'APPLY':
|
|
393
|
+
this.advance();
|
|
394
|
+
stmt = this.applySchemaStatement(startToken);
|
|
395
|
+
break;
|
|
396
|
+
case 'EXPLAIN':
|
|
397
|
+
this.advance();
|
|
398
|
+
stmt = this.explainSchemaStatement(startToken);
|
|
399
|
+
break;
|
|
400
|
+
// --- Add default case ---
|
|
401
|
+
default:
|
|
402
|
+
// If it wasn't a recognized keyword starting the statement
|
|
403
|
+
throw this.error(startToken, `Expected statement type (SELECT, INSERT, UPDATE, DELETE, VALUES, CREATE, etc.), got '${startToken.lexeme}'.`);
|
|
404
|
+
}
|
|
321
405
|
// Attach WITH clause if present and supported
|
|
322
406
|
if (withClause && this.statementSupportsWithClause(stmt)) {
|
|
323
407
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -356,62 +440,56 @@ export class Parser {
|
|
|
356
440
|
this.matchKeyword('INTO'); // Handle missing keyword gracefully
|
|
357
441
|
// Parse the table reference
|
|
358
442
|
const table = this.tableIdentifier();
|
|
359
|
-
const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
|
|
360
443
|
// Parse column list if provided
|
|
361
444
|
let columns;
|
|
362
445
|
if (this.match(TokenType.LPAREN)) {
|
|
363
446
|
columns = [];
|
|
364
447
|
do {
|
|
365
|
-
if (!this.checkIdentifierLike(
|
|
448
|
+
if (!this.checkIdentifierLike(CONTEXTUAL_KEYWORDS)) {
|
|
366
449
|
throw this.error(this.peek(), "Expected column name.");
|
|
367
450
|
}
|
|
368
451
|
columns.push(this.getIdentifierValue(this.advance()));
|
|
369
452
|
} while (this.match(TokenType.COMMA));
|
|
370
453
|
this.consume(TokenType.RPAREN, "Expected ')' after column list.");
|
|
371
454
|
}
|
|
372
|
-
// Parse mutation context assignments if present (after column
|
|
373
|
-
//
|
|
455
|
+
// Parse mutation context assignments and/or tags if present (after column
|
|
456
|
+
// list, before VALUES/SELECT). Either may also appear trailing (after
|
|
457
|
+
// VALUES/SELECT) via parseTrailingWithClauses.
|
|
374
458
|
let contextValues;
|
|
375
|
-
|
|
459
|
+
let tags;
|
|
460
|
+
while (this.matchKeyword('WITH')) {
|
|
376
461
|
if (this.matchKeyword('CONTEXT')) {
|
|
377
462
|
contextValues = this.parseContextAssignments();
|
|
378
463
|
}
|
|
464
|
+
else if (this.matchKeyword('TAGS')) {
|
|
465
|
+
tags = this.parseTags();
|
|
466
|
+
}
|
|
379
467
|
else {
|
|
380
|
-
// Not a WITH CONTEXT clause, backtrack
|
|
468
|
+
// Not a WITH CONTEXT / WITH TAGS clause, backtrack
|
|
381
469
|
this.current--;
|
|
470
|
+
break;
|
|
382
471
|
}
|
|
383
472
|
}
|
|
384
|
-
// Parse VALUES
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
this.
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
else if (this.check(TokenType.SELECT)) { // If current token is SELECT
|
|
404
|
-
// Handle INSERT ... SELECT
|
|
405
|
-
// Consume the SELECT token, as selectStatement expects to start parsing after it.
|
|
406
|
-
// selectKeywordToken will be the actual 'SELECT' token object, used for location.
|
|
407
|
-
const selectKeywordToken = this.advance(); // Consume 'SELECT'
|
|
408
|
-
// Pass the withClause so the embedded SELECT can (via the planner) resolve CTEs defined for the INSERT.
|
|
409
|
-
select = this.selectStatement(selectKeywordToken, withClause);
|
|
410
|
-
lastConsumedToken = this.previous(); // After SELECT statement is parsed
|
|
411
|
-
}
|
|
412
|
-
else {
|
|
413
|
-
throw this.error(this.peek(), "Expected VALUES or SELECT after INSERT.");
|
|
473
|
+
// Parse the source: VALUES / SELECT / INSERT|UPDATE|DELETE with RETURNING.
|
|
474
|
+
// The outer INSERT consumes the resulting relation, so DML sources must
|
|
475
|
+
// carry RETURNING — parseQueryExpr enforces that when requireReturning
|
|
476
|
+
// is true. Pure VALUES / SELECT are passed through unchanged.
|
|
477
|
+
const sourceStartKeyword = this.peek().lexeme.toUpperCase();
|
|
478
|
+
let source;
|
|
479
|
+
switch (sourceStartKeyword) {
|
|
480
|
+
case 'VALUES':
|
|
481
|
+
case 'SELECT':
|
|
482
|
+
source = this.parseQueryExpr(withClause, /*requireReturning*/ false);
|
|
483
|
+
break;
|
|
484
|
+
case 'INSERT':
|
|
485
|
+
case 'UPDATE':
|
|
486
|
+
case 'DELETE':
|
|
487
|
+
source = this.parseQueryExpr(withClause, /*requireReturning*/ true);
|
|
488
|
+
break;
|
|
489
|
+
default:
|
|
490
|
+
throw this.error(this.peek(), "Expected VALUES, SELECT, or DML (with RETURNING) after INSERT.");
|
|
414
491
|
}
|
|
492
|
+
let lastConsumedToken = this.previous(); // After source statement is parsed
|
|
415
493
|
// Parse UPSERT clauses (ON CONFLICT DO ...) - can have multiple
|
|
416
494
|
let upsertClauses;
|
|
417
495
|
while (this.match(TokenType.ON)) {
|
|
@@ -438,6 +516,13 @@ export class Parser {
|
|
|
438
516
|
}
|
|
439
517
|
contextValues = trailingClauses.contextValues;
|
|
440
518
|
}
|
|
519
|
+
if (trailingClauses.tags) {
|
|
520
|
+
if (tags) {
|
|
521
|
+
throw this.error(this.previous(), "Duplicate WITH TAGS clause");
|
|
522
|
+
}
|
|
523
|
+
tags = trailingClauses.tags;
|
|
524
|
+
lastConsumedToken = this.previous(); // After tags
|
|
525
|
+
}
|
|
441
526
|
const schemaPath = trailingClauses.schemaPath;
|
|
442
527
|
if (schemaPath) {
|
|
443
528
|
lastConsumedToken = this.previous(); // After schema path
|
|
@@ -452,13 +537,13 @@ export class Parser {
|
|
|
452
537
|
type: 'insert',
|
|
453
538
|
table,
|
|
454
539
|
columns,
|
|
455
|
-
|
|
456
|
-
select,
|
|
540
|
+
source,
|
|
457
541
|
onConflict,
|
|
458
542
|
upsertClauses,
|
|
459
543
|
returning,
|
|
460
544
|
contextValues,
|
|
461
545
|
schemaPath,
|
|
546
|
+
tags,
|
|
462
547
|
loc: _createLoc(startToken, lastConsumedToken),
|
|
463
548
|
};
|
|
464
549
|
}
|
|
@@ -524,29 +609,19 @@ export class Parser {
|
|
|
524
609
|
*/
|
|
525
610
|
selectStatement(startToken, withClause, isCompoundSubquery = false) {
|
|
526
611
|
const start = startToken ?? this.previous(); // Use provided or the keyword token
|
|
527
|
-
let lastConsumedToken = start; // Initialize lastConsumed
|
|
528
612
|
const distinct = this.matchKeyword('DISTINCT');
|
|
529
|
-
if (distinct)
|
|
530
|
-
lastConsumedToken = this.previous();
|
|
531
613
|
const all = !distinct && this.matchKeyword('ALL');
|
|
532
|
-
if (all)
|
|
533
|
-
lastConsumedToken = this.previous();
|
|
534
614
|
// Parse column list
|
|
535
615
|
const columns = this.columnList();
|
|
536
|
-
if (columns.length > 0)
|
|
537
|
-
lastConsumedToken = this.previous(); // Update after last column element
|
|
538
616
|
// Parse FROM clause if present
|
|
539
617
|
let from;
|
|
540
618
|
if (this.match(TokenType.FROM)) {
|
|
541
619
|
from = this.tableSourceList(withClause);
|
|
542
|
-
if (from.length > 0)
|
|
543
|
-
lastConsumedToken = this.previous(); // After last source/join
|
|
544
620
|
}
|
|
545
621
|
// Parse WHERE clause if present
|
|
546
622
|
let where;
|
|
547
623
|
if (this.match(TokenType.WHERE)) {
|
|
548
624
|
where = this.expression();
|
|
549
|
-
lastConsumedToken = this.previous(); // After where expression
|
|
550
625
|
}
|
|
551
626
|
// Parse GROUP BY clause if present
|
|
552
627
|
let groupBy;
|
|
@@ -555,69 +630,180 @@ export class Parser {
|
|
|
555
630
|
do {
|
|
556
631
|
groupBy.push(this.expression());
|
|
557
632
|
} while (this.match(TokenType.COMMA));
|
|
558
|
-
lastConsumedToken = this.previous(); // After last group by expression
|
|
559
633
|
}
|
|
560
634
|
// Parse HAVING clause if present
|
|
561
635
|
let having;
|
|
562
636
|
if (this.match(TokenType.HAVING)) {
|
|
563
637
|
having = this.expression();
|
|
564
|
-
lastConsumedToken = this.previous(); // After having expression
|
|
565
638
|
}
|
|
566
|
-
// Parse WITH SCHEMA clause if present (must come before ORDER BY/LIMIT
|
|
639
|
+
// Parse WITH SCHEMA clause if present (must come before compound / ORDER BY / LIMIT
|
|
640
|
+
// and is suppressed in compound-subquery position).
|
|
567
641
|
let schemaPath;
|
|
568
642
|
if (!isCompoundSubquery) {
|
|
569
643
|
schemaPath = this.parseSchemaPath();
|
|
570
|
-
if (schemaPath) {
|
|
571
|
-
lastConsumedToken = this.previous(); // After schema path
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
// Check for compound set operations (UNION / INTERSECT / EXCEPT) BEFORE ORDER BY/LIMIT
|
|
575
|
-
let compound;
|
|
576
|
-
if (this.match(TokenType.UNION, TokenType.INTERSECT, TokenType.EXCEPT, TokenType.DIFF)) {
|
|
577
|
-
const tok = this.previous();
|
|
578
|
-
let op;
|
|
579
|
-
if (tok.type === TokenType.UNION) {
|
|
580
|
-
if (this.match(TokenType.ALL)) {
|
|
581
|
-
op = 'unionAll';
|
|
582
|
-
}
|
|
583
|
-
else {
|
|
584
|
-
op = 'union';
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
else if (tok.type === TokenType.INTERSECT) {
|
|
588
|
-
op = 'intersect';
|
|
589
|
-
}
|
|
590
|
-
else if (tok.type === TokenType.EXCEPT) {
|
|
591
|
-
op = 'except';
|
|
592
|
-
}
|
|
593
|
-
else {
|
|
594
|
-
op = 'diff';
|
|
595
|
-
}
|
|
596
|
-
let rightSelect;
|
|
597
|
-
// Handle parenthesized subquery after set operation
|
|
598
|
-
if (this.match(TokenType.LPAREN)) {
|
|
599
|
-
const selectToken = this.consume(TokenType.SELECT, "Expected 'SELECT' in parenthesized set operation.");
|
|
600
|
-
rightSelect = this.selectStatement(selectToken, withClause, true); // Pass true to indicate compound subquery
|
|
601
|
-
this.consume(TokenType.RPAREN, "Expected ')' after parenthesized set operation.");
|
|
602
|
-
}
|
|
603
|
-
else {
|
|
604
|
-
// Handle direct SELECT statement
|
|
605
|
-
const selectStartToken = this.peek();
|
|
606
|
-
if (this.match(TokenType.SELECT)) {
|
|
607
|
-
rightSelect = this.selectStatement(selectStartToken, withClause, true); // Pass true to indicate compound subquery
|
|
608
|
-
}
|
|
609
|
-
else {
|
|
610
|
-
throw this.error(this.peek(), "Expected 'SELECT' or '(' after set operation keyword.");
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
lastConsumedToken = this.previous();
|
|
614
|
-
compound = { op, select: rightSelect };
|
|
615
644
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
645
|
+
const sel = {
|
|
646
|
+
type: 'select',
|
|
647
|
+
columns,
|
|
648
|
+
from,
|
|
649
|
+
where,
|
|
650
|
+
groupBy,
|
|
651
|
+
having,
|
|
652
|
+
distinct,
|
|
653
|
+
all,
|
|
654
|
+
schemaPath,
|
|
655
|
+
loc: _createLoc(start, this.previous()),
|
|
656
|
+
};
|
|
657
|
+
// Compound set operations (UNION / INTERSECT / EXCEPT / DIFF) bind BEFORE
|
|
658
|
+
// ORDER BY / LIMIT — delegate to the shared left-associative tail parser.
|
|
659
|
+
const result = this.parseCompoundTail(sel, withClause, start);
|
|
660
|
+
// ORDER BY / LIMIT / OFFSET apply to the final compound result; suppressed
|
|
661
|
+
// for a compound subquery (they bind to the outer compound).
|
|
662
|
+
this.parseTrailingOrderLimit(result, isCompoundSubquery);
|
|
663
|
+
// Trailing `with defaults (col = expr, …)` — binds to the whole compound,
|
|
664
|
+
// after limit/offset and before any DDL-level `with tags`. Suppressed on a
|
|
665
|
+
// compound leg (it belongs to the outer compound, like trailing ORDER BY).
|
|
666
|
+
if (!isCompoundSubquery) {
|
|
667
|
+
const defaults = this.parseDefaultsClause();
|
|
668
|
+
if (defaults)
|
|
669
|
+
result.defaults = defaults;
|
|
670
|
+
}
|
|
671
|
+
result.loc = _createLoc(start, this.previous());
|
|
672
|
+
return result;
|
|
673
|
+
}
|
|
674
|
+
/** True when the next token is a compound set-operation keyword. */
|
|
675
|
+
checkCompoundOperator() {
|
|
676
|
+
return this.check(TokenType.UNION)
|
|
677
|
+
|| this.check(TokenType.INTERSECT)
|
|
678
|
+
|| this.check(TokenType.EXCEPT)
|
|
679
|
+
|| this.check(TokenType.DIFF);
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Decode a compound set-operation operator the caller has just consumed
|
|
683
|
+
* (via `match`), plus the optional `<setop> exists <branch> as <name>`
|
|
684
|
+
* membership-column clause(s) that sit between the operator keyword and the
|
|
685
|
+
* right leg. One-token lookahead (`exists` followed by `left`/`right`, never
|
|
686
|
+
* `(`) distinguishes the membership clause from the `exists (<subquery>)`
|
|
687
|
+
* predicate, which never legally begins a compound leg.
|
|
688
|
+
*/
|
|
689
|
+
parseCompoundOperator() {
|
|
690
|
+
const tok = this.previous();
|
|
691
|
+
let op;
|
|
692
|
+
if (tok.type === TokenType.UNION) {
|
|
693
|
+
op = this.match(TokenType.ALL) ? 'unionAll' : 'union';
|
|
694
|
+
}
|
|
695
|
+
else if (tok.type === TokenType.INTERSECT) {
|
|
696
|
+
op = 'intersect';
|
|
697
|
+
}
|
|
698
|
+
else if (tok.type === TokenType.EXCEPT) {
|
|
699
|
+
op = 'except';
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
op = 'diff';
|
|
703
|
+
}
|
|
704
|
+
const existence = this.setOpMembershipClauses(op);
|
|
705
|
+
return existence ? { op, existence } : { op };
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Reads a single `query-term` operand of a set operation: either a
|
|
709
|
+
* parenthesized query expression `( <query-expr> )` (full recursion — WITH,
|
|
710
|
+
* nested compound, and the inner's OWN trailing ORDER BY / LIMIT all bind
|
|
711
|
+
* inside the parens) or a bare keyword-led operand (SELECT / VALUES / DML).
|
|
712
|
+
*
|
|
713
|
+
* Bare SELECT / VALUES legs parse with `isCompoundSubquery=true` so a
|
|
714
|
+
* trailing ORDER BY / LIMIT binds to the OUTER compound, and a bare SELECT
|
|
715
|
+
* leg greedily consumes the remaining unparenthesized chain (preserving the
|
|
716
|
+
* historical right-leaning shape). `requireReturning` is propagated so a
|
|
717
|
+
* DML operand without RETURNING in a leg position is rejected.
|
|
718
|
+
*/
|
|
719
|
+
parseCompoundOperand(withClause, requireReturning) {
|
|
720
|
+
if (this.check(TokenType.LPAREN)) {
|
|
721
|
+
this.advance();
|
|
722
|
+
const inner = this.parseQueryExpr(withClause, requireReturning);
|
|
723
|
+
this.consume(TokenType.RPAREN, "Expected ')' after parenthesized query expression.");
|
|
724
|
+
return inner;
|
|
725
|
+
}
|
|
726
|
+
const start = this.peek();
|
|
727
|
+
if (this.check(TokenType.SELECT)) {
|
|
728
|
+
this.advance();
|
|
729
|
+
return this.selectStatement(start, withClause, /*isCompoundSubquery*/ true);
|
|
730
|
+
}
|
|
731
|
+
if (this.check(TokenType.VALUES)) {
|
|
732
|
+
this.advance();
|
|
733
|
+
return this.valuesStatementWithOptionalCompound(start, withClause, /*isCompoundSubquery*/ true);
|
|
734
|
+
}
|
|
735
|
+
if (this.check(TokenType.WITH) || this.check(TokenType.INSERT) || this.check(TokenType.UPDATE) || this.check(TokenType.DELETE)) {
|
|
736
|
+
return this.parseQueryExpr(undefined, requireReturning);
|
|
737
|
+
}
|
|
738
|
+
throw this.error(this.peek(), "Expected SELECT, VALUES, a DML statement, or '(' to begin a query operand.");
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Wraps an arbitrary query expression as `select * from (<inner>) as
|
|
742
|
+
* <synthetic alias>` so the SELECT-level `compound` / ORDER BY / LIMIT slots
|
|
743
|
+
* apply to it. Generalized from the VALUES-compound wrapper; the synthetic
|
|
744
|
+
* alias is collision-proof per the inner's start offset (nested wraps live in
|
|
745
|
+
* distinct subquery scopes, so identical aliases never collide).
|
|
746
|
+
*/
|
|
747
|
+
wrapAsSubquerySelect(inner, startToken) {
|
|
748
|
+
const syntheticAlias = `values_${startToken.startOffset}`;
|
|
749
|
+
return {
|
|
750
|
+
type: 'select',
|
|
751
|
+
columns: [{ type: 'all' }],
|
|
752
|
+
from: [{
|
|
753
|
+
type: 'subquerySource',
|
|
754
|
+
subquery: inner,
|
|
755
|
+
alias: syntheticAlias,
|
|
756
|
+
loc: inner.loc,
|
|
757
|
+
}],
|
|
758
|
+
loc: inner.loc,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Left-associative tail parser shared by every compound site. Consumes a
|
|
763
|
+
* chain of `compound-op [membership] query-term` after an already-parsed
|
|
764
|
+
* left operand and returns the resulting SELECT.
|
|
765
|
+
*
|
|
766
|
+
* A parenthesized operand iterates the loop (left-associative), wrapping the
|
|
767
|
+
* accumulator whenever it cannot host the new compound — either it is not a
|
|
768
|
+
* plain SELECT (e.g. a parenthesized VALUES/DML/compound operand) or its
|
|
769
|
+
* `compound` slot is already taken. The first BARE keyword operand greedily
|
|
770
|
+
* consumes the rest of the chain itself (its own `selectStatement` /
|
|
771
|
+
* `valuesStatementWithOptionalCompound` recursion), so an unparenthesized
|
|
772
|
+
* chain keeps today's right-leaning shape byte-for-byte. The user's explicit
|
|
773
|
+
* parentheses are the only escape hatch into left-grouping.
|
|
774
|
+
*/
|
|
775
|
+
parseCompoundTail(left, withClause, startToken) {
|
|
776
|
+
let acc = left;
|
|
777
|
+
while (this.match(TokenType.UNION, TokenType.INTERSECT, TokenType.EXCEPT, TokenType.DIFF)) {
|
|
778
|
+
const { op, existence } = this.parseCompoundOperator();
|
|
779
|
+
const parenthesized = this.check(TokenType.LPAREN);
|
|
780
|
+
const right = this.parseCompoundOperand(withClause, /*requireReturning*/ true);
|
|
781
|
+
if (acc.type !== 'select' || acc.compound) {
|
|
782
|
+
acc = this.wrapAsSubquerySelect(acc, startToken);
|
|
783
|
+
}
|
|
784
|
+
acc.compound = existence ? { op, select: right, existence } : { op, select: right };
|
|
785
|
+
if (!parenthesized)
|
|
786
|
+
break; // a bare operand already consumed the remaining chain
|
|
787
|
+
}
|
|
788
|
+
// `left` is a SelectStmt at every call site that may take zero iterations
|
|
789
|
+
// (selectStatement / continueSelectAfterFrom); the parenthesized entry only
|
|
790
|
+
// delegates here once a compound operator follows, and any iteration leaves
|
|
791
|
+
// `acc` a SELECT — so the result is always a SelectStmt.
|
|
792
|
+
return acc;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Parses the trailing ORDER BY / LIMIT / OFFSET clauses that apply to the
|
|
796
|
+
* final result of a (possibly compound) SELECT, mutating `sel` in place.
|
|
797
|
+
* Suppressed entirely when `isCompoundSubquery` is set — those clauses then
|
|
798
|
+
* bind to the enclosing compound, not this leg. Shared by `selectStatement`
|
|
799
|
+
* and `continueSelectAfterFrom`.
|
|
800
|
+
*/
|
|
801
|
+
parseTrailingOrderLimit(sel, isCompoundSubquery) {
|
|
802
|
+
if (isCompoundSubquery)
|
|
803
|
+
return;
|
|
804
|
+
// ORDER BY applies to the final result after compound operations.
|
|
805
|
+
if (this.match(TokenType.ORDER) && this.consume(TokenType.BY, "Expected 'BY' after 'ORDER'.")) {
|
|
806
|
+
sel.orderBy = [];
|
|
621
807
|
do {
|
|
622
808
|
const expr = this.expression();
|
|
623
809
|
const direction = this.match(TokenType.DESC) ? 'desc' :
|
|
@@ -639,64 +825,42 @@ export class Parser {
|
|
|
639
825
|
if (nulls) {
|
|
640
826
|
orderClause.nulls = nulls;
|
|
641
827
|
}
|
|
642
|
-
orderBy.push(orderClause);
|
|
828
|
+
sel.orderBy.push(orderClause);
|
|
643
829
|
} while (this.match(TokenType.COMMA));
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
let limit;
|
|
649
|
-
let offset;
|
|
650
|
-
if (!isCompoundSubquery && this.match(TokenType.LIMIT)) {
|
|
651
|
-
limit = this.expression();
|
|
652
|
-
lastConsumedToken = this.previous(); // After limit expression
|
|
830
|
+
}
|
|
831
|
+
// LIMIT applies to the final result after compound operations.
|
|
832
|
+
if (this.match(TokenType.LIMIT)) {
|
|
833
|
+
sel.limit = this.expression();
|
|
653
834
|
// LIMIT x OFFSET y syntax
|
|
654
835
|
if (this.match(TokenType.OFFSET)) {
|
|
655
|
-
offset = this.expression();
|
|
656
|
-
lastConsumedToken = this.previous(); // After offset expression
|
|
836
|
+
sel.offset = this.expression();
|
|
657
837
|
}
|
|
658
838
|
// LIMIT x, y syntax (x is offset, y is limit)
|
|
659
839
|
else if (this.match(TokenType.COMMA)) {
|
|
660
|
-
offset = limit;
|
|
661
|
-
limit = this.expression();
|
|
662
|
-
lastConsumedToken = this.previous(); // After second limit expression
|
|
840
|
+
sel.offset = sel.limit;
|
|
841
|
+
sel.limit = this.expression();
|
|
663
842
|
}
|
|
664
843
|
}
|
|
665
|
-
return {
|
|
666
|
-
type: 'select',
|
|
667
|
-
columns,
|
|
668
|
-
from,
|
|
669
|
-
where,
|
|
670
|
-
groupBy,
|
|
671
|
-
having,
|
|
672
|
-
orderBy,
|
|
673
|
-
limit,
|
|
674
|
-
offset,
|
|
675
|
-
distinct,
|
|
676
|
-
all,
|
|
677
|
-
compound,
|
|
678
|
-
schemaPath,
|
|
679
|
-
loc: _createLoc(start, lastConsumedToken),
|
|
680
|
-
};
|
|
681
844
|
}
|
|
682
845
|
/**
|
|
683
846
|
* Parse a comma-separated list of result columns for SELECT
|
|
684
847
|
*/
|
|
685
848
|
columnList() {
|
|
686
849
|
const columns = [];
|
|
687
|
-
const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
|
|
688
850
|
do {
|
|
689
851
|
// Handle wildcard: * or table.*
|
|
690
852
|
if (this.match(TokenType.ASTERISK)) {
|
|
691
853
|
columns.push({ type: 'all' });
|
|
854
|
+
this.rejectInverseClauseOnStar();
|
|
692
855
|
}
|
|
693
856
|
// Handle table.* syntax
|
|
694
|
-
else if (this.checkIdentifierLike(
|
|
857
|
+
else if (this.checkIdentifierLike(CONTEXTUAL_KEYWORDS) && this.checkNext(1, TokenType.DOT) &&
|
|
695
858
|
this.checkNext(2, TokenType.ASTERISK)) {
|
|
696
|
-
const table = this.consumeIdentifier(
|
|
859
|
+
const table = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected table name before '.*'.");
|
|
697
860
|
this.advance(); // consume DOT
|
|
698
861
|
this.advance(); // consume ASTERISK
|
|
699
862
|
columns.push({ type: 'all', table });
|
|
863
|
+
this.rejectInverseClauseOnStar();
|
|
700
864
|
}
|
|
701
865
|
// Handle regular column expression
|
|
702
866
|
else {
|
|
@@ -704,7 +868,7 @@ export class Parser {
|
|
|
704
868
|
let alias;
|
|
705
869
|
// Handle AS alias or just alias
|
|
706
870
|
if (this.match(TokenType.AS)) {
|
|
707
|
-
if (this.checkIdentifierLike(
|
|
871
|
+
if (this.checkIdentifierLike(CONTEXTUAL_KEYWORDS) || this.check(TokenType.STRING)) {
|
|
708
872
|
const aliasToken = this.advance();
|
|
709
873
|
// For STRING tokens, use literal; for identifiers, use getIdentifierValue
|
|
710
874
|
alias = aliasToken.type === TokenType.STRING
|
|
@@ -724,7 +888,10 @@ export class Parser {
|
|
|
724
888
|
const aliasToken = this.advance();
|
|
725
889
|
alias = this.getIdentifierValue(aliasToken);
|
|
726
890
|
}
|
|
727
|
-
|
|
891
|
+
// Optional trailing `with inverse (col = expr, …)` — authored
|
|
892
|
+
// write-back expressions (docs/view-updateability.md § Authored inverses)
|
|
893
|
+
const inverse = this.parseInverseClause();
|
|
894
|
+
columns.push({ type: 'column', expr, alias, inverse });
|
|
728
895
|
}
|
|
729
896
|
} while (this.match(TokenType.COMMA));
|
|
730
897
|
return columns;
|
|
@@ -737,7 +904,7 @@ export class Parser {
|
|
|
737
904
|
let schema;
|
|
738
905
|
let name;
|
|
739
906
|
let endToken = startToken;
|
|
740
|
-
const contextualKeywords = [
|
|
907
|
+
const contextualKeywords = [...CONTEXTUAL_KEYWORDS, 'temp', 'temporary'];
|
|
741
908
|
// Check for schema.table pattern
|
|
742
909
|
if (this.checkIdentifierLike(contextualKeywords) && this.checkNext(1, TokenType.DOT)) {
|
|
743
910
|
schema = this.consumeIdentifier(contextualKeywords, "Expected schema name.");
|
|
@@ -780,23 +947,15 @@ export class Parser {
|
|
|
780
947
|
*/
|
|
781
948
|
tableSource(withClause) {
|
|
782
949
|
const startToken = this.peek();
|
|
783
|
-
|
|
784
|
-
//
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
const nextTokenType = this.tokens[lookahead].type;
|
|
790
|
-
if (nextTokenType === TokenType.SELECT || nextTokenType === TokenType.VALUES || nextTokenType === TokenType.WITH) {
|
|
791
|
-
return this.subquerySource(startToken, withClause);
|
|
792
|
-
}
|
|
793
|
-
else if (nextTokenType === TokenType.INSERT || nextTokenType === TokenType.UPDATE || nextTokenType === TokenType.DELETE) {
|
|
794
|
-
return this.mutatingSubquerySource(startToken, withClause);
|
|
795
|
-
}
|
|
796
|
-
}
|
|
950
|
+
// Subquery: any QueryExpr in parens. Decision is made on the token
|
|
951
|
+
// immediately after `(`; all relation-producing forms (SELECT, VALUES,
|
|
952
|
+
// WITH …, INSERT|UPDATE|DELETE w/ RETURNING) flow through the same
|
|
953
|
+
// subquerySource path.
|
|
954
|
+
if (this.startsParenSubquery()) {
|
|
955
|
+
return this.subquerySource(startToken, withClause);
|
|
797
956
|
}
|
|
798
957
|
// Check for function call syntax: IDENTIFIER (
|
|
799
|
-
if (this.checkIdentifierLike(
|
|
958
|
+
if (this.checkIdentifierLike(CONTEXTUAL_KEYWORDS) && this.checkNext(1, TokenType.LPAREN)) {
|
|
800
959
|
return this.functionSource(startToken);
|
|
801
960
|
}
|
|
802
961
|
// Otherwise, assume it's a standard table source
|
|
@@ -804,31 +963,22 @@ export class Parser {
|
|
|
804
963
|
return this.standardTableSource(startToken);
|
|
805
964
|
}
|
|
806
965
|
}
|
|
807
|
-
/**
|
|
808
|
-
|
|
966
|
+
/**
|
|
967
|
+
* Parses a subquery source: `(<QueryExpr>) [AS alias [(cols)]]`.
|
|
968
|
+
*
|
|
969
|
+
* Accepts any relation-producing form (SELECT, VALUES, WITH …, or
|
|
970
|
+
* INSERT/UPDATE/DELETE with RETURNING). The DML branch is enforced to
|
|
971
|
+
* carry RETURNING because the outer FROM-clause position consumes a
|
|
972
|
+
* relation, not a side-effect.
|
|
973
|
+
*
|
|
974
|
+
* `requireAlias` mandates an explicit user-written alias rather than synthesizing
|
|
975
|
+
* a default — set for an inline-subquery DML *write target* (`update (select …) as
|
|
976
|
+
* v set …`), whose `alias` is user-meaningful (the `where`/`set` reference it). A
|
|
977
|
+
* FROM-clause subquery leaves it false and keeps the generated-alias fallback.
|
|
978
|
+
*/
|
|
979
|
+
subquerySource(startToken, withClause, requireAlias = false) {
|
|
809
980
|
this.consume(TokenType.LPAREN, "Expected '(' before subquery.");
|
|
810
|
-
|
|
811
|
-
if (this.check(TokenType.WITH)) {
|
|
812
|
-
// (WITH ... SELECT ...) — the WITH attaches to the inner SELECT.
|
|
813
|
-
const innerWith = this.tryParseWithClause();
|
|
814
|
-
const selectToken = this.consume(TokenType.SELECT, "Expected 'SELECT' after WITH in subquery.");
|
|
815
|
-
const sel = this.selectStatement(selectToken, innerWith);
|
|
816
|
-
sel.withClause = innerWith;
|
|
817
|
-
subquery = sel;
|
|
818
|
-
}
|
|
819
|
-
else if (this.check(TokenType.SELECT)) {
|
|
820
|
-
// Consume the SELECT token and pass it as startToken to selectStatement
|
|
821
|
-
const selectToken = this.advance();
|
|
822
|
-
subquery = this.selectStatement(selectToken, withClause);
|
|
823
|
-
}
|
|
824
|
-
else if (this.check(TokenType.VALUES)) {
|
|
825
|
-
// Handle VALUES subquery
|
|
826
|
-
const valuesToken = this.advance();
|
|
827
|
-
subquery = this.valuesStatement(valuesToken);
|
|
828
|
-
}
|
|
829
|
-
else {
|
|
830
|
-
throw this.error(this.peek(), "Expected 'SELECT', 'VALUES', or 'WITH' in subquery.");
|
|
831
|
-
}
|
|
981
|
+
const subquery = this.parseQueryExpr(withClause, /*requireReturning*/ true);
|
|
832
982
|
this.consume(TokenType.RPAREN, "Expected ')' after subquery.");
|
|
833
983
|
// Parse optional alias for subquery
|
|
834
984
|
let alias;
|
|
@@ -846,17 +996,24 @@ export class Parser {
|
|
|
846
996
|
!this.isEndOfClause()) {
|
|
847
997
|
alias = this.getIdentifierValue(this.advance());
|
|
848
998
|
}
|
|
999
|
+
else if (requireAlias) {
|
|
1000
|
+
// A subquery write target's alias is mandatory and user-meaningful — never a
|
|
1001
|
+
// generated default (the `where`/`set` reference it). Reject the bare form.
|
|
1002
|
+
throw this.error(this.peek(), "a subquery UPDATE/DELETE write target requires an alias, e.g. (select …) as v");
|
|
1003
|
+
}
|
|
849
1004
|
else {
|
|
850
|
-
// Generate a default alias if none provided
|
|
851
|
-
|
|
1005
|
+
// Generate a default alias if none provided. Keep separate prefixes
|
|
1006
|
+
// for read-only vs mutating bodies so generated aliases stay
|
|
1007
|
+
// distinguishable when surfacing in diagnostics.
|
|
1008
|
+
const isMutating = subquery.type === 'insert' || subquery.type === 'update' || subquery.type === 'delete';
|
|
1009
|
+
alias = `${isMutating ? 'mutating_subquery' : 'subquery'}_${startToken.startOffset}`;
|
|
852
1010
|
}
|
|
853
1011
|
// Parse optional column list after alias: AS alias(col1, col2, ...)
|
|
854
1012
|
if (this.match(TokenType.LPAREN)) {
|
|
855
1013
|
columns = [];
|
|
856
|
-
const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
|
|
857
1014
|
if (!this.check(TokenType.RPAREN)) {
|
|
858
1015
|
do {
|
|
859
|
-
columns.push(this.consumeIdentifier(
|
|
1016
|
+
columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in alias column list."));
|
|
860
1017
|
} while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
|
|
861
1018
|
}
|
|
862
1019
|
this.consume(TokenType.RPAREN, "Expected ')' after alias column list.");
|
|
@@ -870,80 +1027,49 @@ export class Parser {
|
|
|
870
1027
|
loc: _createLoc(startToken, endToken),
|
|
871
1028
|
};
|
|
872
1029
|
}
|
|
873
|
-
/**
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
else if (this.checkIdentifierLike([]) &&
|
|
907
|
-
!this.checkNext(1, TokenType.DOT) &&
|
|
908
|
-
!this.checkNext(1, TokenType.COMMA) &&
|
|
909
|
-
!this.isJoinToken() &&
|
|
910
|
-
!this.isEndOfClause()) {
|
|
911
|
-
alias = this.getIdentifierValue(this.advance());
|
|
912
|
-
}
|
|
913
|
-
else {
|
|
914
|
-
// Generate a default alias if none provided
|
|
915
|
-
alias = `mutating_subquery_${startToken.startOffset}`;
|
|
916
|
-
}
|
|
917
|
-
// Parse optional column list after alias: AS alias(col1, col2, ...)
|
|
918
|
-
if (this.match(TokenType.LPAREN)) {
|
|
919
|
-
columns = [];
|
|
920
|
-
const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
|
|
921
|
-
if (!this.check(TokenType.RPAREN)) {
|
|
922
|
-
do {
|
|
923
|
-
columns.push(this.consumeIdentifier(contextualKeywords, "Expected column name in alias column list."));
|
|
924
|
-
} while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
|
|
925
|
-
}
|
|
926
|
-
this.consume(TokenType.RPAREN, "Expected ')' after alias column list.");
|
|
927
|
-
}
|
|
928
|
-
const endToken = this.previous();
|
|
929
|
-
return {
|
|
930
|
-
type: 'mutatingSubquerySource',
|
|
931
|
-
stmt,
|
|
932
|
-
alias,
|
|
933
|
-
columns,
|
|
934
|
-
loc: _createLoc(startToken, endToken),
|
|
935
|
-
};
|
|
1030
|
+
/**
|
|
1031
|
+
* Detect and parse a leading inline-subquery DML write target — `(<query>) as v` —
|
|
1032
|
+
* for `update`/`delete`, or `undefined` when the next tokens are not a `(` followed
|
|
1033
|
+
* by a relation-producing keyword (the SAME lookahead {@link tableSource} uses for a
|
|
1034
|
+
* FROM subquery). The alias is mandatory (`requireAlias`); the body re-uses the
|
|
1035
|
+
* shared {@link subquerySource} production, so it requires RETURNING on a DML body
|
|
1036
|
+
* (rejected as a write target downstream) and parses the `as v` / `v(a,b)` alias.
|
|
1037
|
+
*/
|
|
1038
|
+
subqueryDmlTarget(withClause) {
|
|
1039
|
+
if (!this.startsParenSubquery())
|
|
1040
|
+
return undefined;
|
|
1041
|
+
return this.subquerySource(this.peek(), withClause, /*requireAlias*/ true);
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* True when the cursor is at `(` followed by a relation-producing keyword
|
|
1045
|
+
* (`SELECT` / `VALUES` / `WITH` / a DML keyword) — i.e. the start of a
|
|
1046
|
+
* parenthesized subquery source. Shared by the FROM-clause {@link tableSource}
|
|
1047
|
+
* and the inline-subquery DML target {@link subqueryDmlTarget} so the lookahead
|
|
1048
|
+
* set stays defined once.
|
|
1049
|
+
*/
|
|
1050
|
+
startsParenSubquery() {
|
|
1051
|
+
if (!this.check(TokenType.LPAREN))
|
|
1052
|
+
return false;
|
|
1053
|
+
const lookahead = this.current + 1;
|
|
1054
|
+
if (lookahead >= this.tokens.length)
|
|
1055
|
+
return false;
|
|
1056
|
+
const nextTokenType = this.tokens[lookahead].type;
|
|
1057
|
+
return (nextTokenType === TokenType.SELECT
|
|
1058
|
+
|| nextTokenType === TokenType.VALUES
|
|
1059
|
+
|| nextTokenType === TokenType.WITH
|
|
1060
|
+
|| nextTokenType === TokenType.INSERT
|
|
1061
|
+
|| nextTokenType === TokenType.UPDATE
|
|
1062
|
+
|| nextTokenType === TokenType.DELETE);
|
|
936
1063
|
}
|
|
937
1064
|
/** Parses a standard table source (schema.table or table) */
|
|
938
1065
|
standardTableSource(startToken) {
|
|
939
|
-
const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
|
|
940
1066
|
// Parse table name (potentially schema-qualified)
|
|
941
1067
|
const table = this.tableIdentifier();
|
|
942
1068
|
let endToken = this.previous(); // Initialize endToken after parsing table identifier
|
|
943
1069
|
// Parse optional alias
|
|
944
1070
|
let alias;
|
|
945
1071
|
if (this.match(TokenType.AS)) {
|
|
946
|
-
if (!this.checkIdentifierLike(
|
|
1072
|
+
if (!this.checkIdentifierLike(CONTEXTUAL_KEYWORDS)) {
|
|
947
1073
|
throw this.error(this.peek(), "Expected alias after 'AS'.");
|
|
948
1074
|
}
|
|
949
1075
|
const aliasToken = this.advance();
|
|
@@ -968,7 +1094,6 @@ export class Parser {
|
|
|
968
1094
|
}
|
|
969
1095
|
/** Parses a table-valued function source: name(arg1, ...) [AS alias] */
|
|
970
1096
|
functionSource(startToken) {
|
|
971
|
-
const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
|
|
972
1097
|
const name = this.tableIdentifier(); // name has its own loc
|
|
973
1098
|
let endToken = this.previous(); // Initialize endToken after parsing function identifier
|
|
974
1099
|
this.consume(TokenType.LPAREN, "Expected '(' after table function name.");
|
|
@@ -999,7 +1124,7 @@ export class Parser {
|
|
|
999
1124
|
let alias;
|
|
1000
1125
|
let columns;
|
|
1001
1126
|
if (this.match(TokenType.AS)) {
|
|
1002
|
-
if (!this.checkIdentifierLike(
|
|
1127
|
+
if (!this.checkIdentifierLike(CONTEXTUAL_KEYWORDS)) {
|
|
1003
1128
|
throw this.error(this.peek(), "Expected alias after 'AS'.");
|
|
1004
1129
|
}
|
|
1005
1130
|
const aliasToken = this.advance();
|
|
@@ -1018,10 +1143,9 @@ export class Parser {
|
|
|
1018
1143
|
// Optional column list after alias: alias(col1, col2, ...)
|
|
1019
1144
|
if (alias && this.match(TokenType.LPAREN)) {
|
|
1020
1145
|
columns = [];
|
|
1021
|
-
const colKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
|
|
1022
1146
|
if (!this.check(TokenType.RPAREN)) {
|
|
1023
1147
|
do {
|
|
1024
|
-
columns.push(this.consumeIdentifier(
|
|
1148
|
+
columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in alias column list."));
|
|
1025
1149
|
} while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
|
|
1026
1150
|
}
|
|
1027
1151
|
endToken = this.consume(TokenType.RPAREN, "Expected ')' after alias column list.");
|
|
@@ -1077,15 +1201,22 @@ export class Parser {
|
|
|
1077
1201
|
else if (this.match(TokenType.USING)) {
|
|
1078
1202
|
this.consume(TokenType.LPAREN, "Expected '(' after 'USING'.");
|
|
1079
1203
|
columns = [];
|
|
1080
|
-
const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
|
|
1081
1204
|
do {
|
|
1082
|
-
columns.push(this.consumeIdentifier(
|
|
1205
|
+
columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name."));
|
|
1083
1206
|
} while (this.match(TokenType.COMMA));
|
|
1084
1207
|
endToken = this.consume(TokenType.RPAREN, "Expected ')' after columns.");
|
|
1085
1208
|
}
|
|
1086
1209
|
else if (joinType !== 'cross') {
|
|
1087
1210
|
throw this.error(this.peek(), "Expected 'ON' or 'USING' after JOIN.");
|
|
1088
1211
|
}
|
|
1212
|
+
// Optional `exists [left|right] as <name>` existence column clause(s), after a
|
|
1213
|
+
// complete ON/USING predicate. One-token lookahead after `exists` (an `as` or
|
|
1214
|
+
// side token, never `(`) distinguishes this from the `exists (<subquery>)`
|
|
1215
|
+
// predicate; the comma form is recognised only when followed by another
|
|
1216
|
+
// `exists`, so a genuine new FROM source comma is left for `tableSourceList`.
|
|
1217
|
+
const existence = this.joinExistenceClauses(joinType);
|
|
1218
|
+
if (existence)
|
|
1219
|
+
endToken = this.previous();
|
|
1089
1220
|
return {
|
|
1090
1221
|
type: 'join',
|
|
1091
1222
|
joinType,
|
|
@@ -1094,9 +1225,99 @@ export class Parser {
|
|
|
1094
1225
|
condition,
|
|
1095
1226
|
columns,
|
|
1096
1227
|
isLateral: isLateral || undefined,
|
|
1228
|
+
existence,
|
|
1097
1229
|
loc: _createLoc(joinStartToken, endToken),
|
|
1098
1230
|
};
|
|
1099
1231
|
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Parse the optional comma-separated `exists [left|right] as <name>` clauses
|
|
1234
|
+
* trailing a join. Returns `undefined` when none are present. Resolves and
|
|
1235
|
+
* validates the side against the join type (default = the unique non-preserved
|
|
1236
|
+
* side; explicit side required for `full`; `inner`/`cross` rejected — no
|
|
1237
|
+
* null-extension means the flag would be a meaningless constant `true`).
|
|
1238
|
+
*/
|
|
1239
|
+
joinExistenceClauses(joinType) {
|
|
1240
|
+
// `exists` here must be followed by `as` or a side token, never `(` (which
|
|
1241
|
+
// would be the `exists (<subquery>)` predicate). Bail before consuming if the
|
|
1242
|
+
// lookahead does not match the clause shape.
|
|
1243
|
+
const atExistenceClause = () => this.check(TokenType.EXISTS) &&
|
|
1244
|
+
(this.checkNext(1, TokenType.AS) || this.checkNext(1, TokenType.LEFT) || this.checkNext(1, TokenType.RIGHT));
|
|
1245
|
+
if (!atExistenceClause())
|
|
1246
|
+
return undefined;
|
|
1247
|
+
const result = [];
|
|
1248
|
+
do {
|
|
1249
|
+
this.consume(TokenType.EXISTS, "Expected 'exists'.");
|
|
1250
|
+
let explicitSide;
|
|
1251
|
+
if (this.match(TokenType.LEFT))
|
|
1252
|
+
explicitSide = 'left';
|
|
1253
|
+
else if (this.match(TokenType.RIGHT))
|
|
1254
|
+
explicitSide = 'right';
|
|
1255
|
+
this.consume(TokenType.AS, "Expected 'as' after 'exists' join existence clause.");
|
|
1256
|
+
const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected name after 'exists ... as'.");
|
|
1257
|
+
result.push({ side: this.resolveExistenceSide(joinType, explicitSide), name });
|
|
1258
|
+
// Continue only on `, exists ...`; a plain comma starts a new FROM source.
|
|
1259
|
+
} while (this.check(TokenType.COMMA) && this.checkNext(1, TokenType.EXISTS) && this.advance());
|
|
1260
|
+
return result;
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Resolve and validate the side of an `exists [<side>] as` join existence
|
|
1264
|
+
* clause. The flag must reference a null-extendable (non-preserved) side.
|
|
1265
|
+
*/
|
|
1266
|
+
resolveExistenceSide(joinType, explicitSide) {
|
|
1267
|
+
// Non-preserved (null-extendable) sides per join type.
|
|
1268
|
+
const nonPreserved = joinType === 'left' ? ['right']
|
|
1269
|
+
: joinType === 'right' ? ['left']
|
|
1270
|
+
: joinType === 'full' ? ['left', 'right']
|
|
1271
|
+
: []; // inner / cross: neither side null-extends
|
|
1272
|
+
if (nonPreserved.length === 0) {
|
|
1273
|
+
throw this.error(this.previous(), `'exists ... as' is not valid on an ${joinType.toUpperCase()} join (no side is null-extended, so the flag would be a constant true)`);
|
|
1274
|
+
}
|
|
1275
|
+
if (explicitSide) {
|
|
1276
|
+
if (!nonPreserved.includes(explicitSide)) {
|
|
1277
|
+
throw this.error(this.previous(), `'exists ${explicitSide} as' references the preserved side of a ${joinType.toUpperCase()} join; only the non-preserved side (${nonPreserved.join('/')}) has a meaningful match flag`);
|
|
1278
|
+
}
|
|
1279
|
+
return explicitSide;
|
|
1280
|
+
}
|
|
1281
|
+
if (nonPreserved.length > 1) {
|
|
1282
|
+
throw this.error(this.previous(), `'exists as' is ambiguous on a ${joinType.toUpperCase()} join — specify 'exists left as' or 'exists right as'`);
|
|
1283
|
+
}
|
|
1284
|
+
return nonPreserved[0];
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Parse the optional comma-separated `exists <branch> as <name>` membership
|
|
1288
|
+
* clauses that sit between a set-operation keyword and its right leg. Returns
|
|
1289
|
+
* `undefined` when none are present. The `branch` is mandatory (`left` = the leg
|
|
1290
|
+
* already parsed before the operator, `right` = the operand that follows) — there
|
|
1291
|
+
* is no elided form, so `exists` here is ALWAYS followed by `left`/`right`, never
|
|
1292
|
+
* `(`; that one-token lookahead distinguishes the clause from the
|
|
1293
|
+
* `exists (<subquery>)` predicate. Rejected on `diff` (symmetric difference
|
|
1294
|
+
* desugars to two `except`s, so membership is ambiguous).
|
|
1295
|
+
*/
|
|
1296
|
+
setOpMembershipClauses(op) {
|
|
1297
|
+
const atMembershipClause = () => this.check(TokenType.EXISTS) &&
|
|
1298
|
+
(this.checkNext(1, TokenType.LEFT) || this.checkNext(1, TokenType.RIGHT));
|
|
1299
|
+
if (!atMembershipClause())
|
|
1300
|
+
return undefined;
|
|
1301
|
+
if (op === 'diff') {
|
|
1302
|
+
throw this.error(this.peek(), "'exists <branch> as' membership columns are not valid on DIFF — symmetric difference desugars to two EXCEPTs, so branch membership is ambiguous");
|
|
1303
|
+
}
|
|
1304
|
+
const result = [];
|
|
1305
|
+
do {
|
|
1306
|
+
this.consume(TokenType.EXISTS, "Expected 'exists'.");
|
|
1307
|
+
let branch;
|
|
1308
|
+
if (this.match(TokenType.LEFT))
|
|
1309
|
+
branch = 'left';
|
|
1310
|
+
else if (this.match(TokenType.RIGHT))
|
|
1311
|
+
branch = 'right';
|
|
1312
|
+
else
|
|
1313
|
+
throw this.error(this.peek(), "Expected 'left' or 'right' after 'exists' in a set-operation membership clause.");
|
|
1314
|
+
this.consume(TokenType.AS, "Expected 'as' after 'exists <branch>' membership clause.");
|
|
1315
|
+
const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected name after 'exists <branch> as'.");
|
|
1316
|
+
result.push({ branch, name });
|
|
1317
|
+
// Continue only on `, exists ...`; a plain comma starts the next leg / clause boundary.
|
|
1318
|
+
} while (this.check(TokenType.COMMA) && this.checkNext(1, TokenType.EXISTS) && this.advance());
|
|
1319
|
+
return result;
|
|
1320
|
+
}
|
|
1100
1321
|
/**
|
|
1101
1322
|
* Parse a left-associative chain of binary operators.
|
|
1102
1323
|
* Captures the start token before parsing the first operand, avoiding O(n) token lookups.
|
|
@@ -1146,12 +1367,16 @@ export class Parser {
|
|
|
1146
1367
|
const right = this.notExpression();
|
|
1147
1368
|
return { type: 'unary', operator: 'NOT', expr: right, loc: _createLoc(operatorToken, this.previous()) };
|
|
1148
1369
|
}
|
|
1149
|
-
return this.
|
|
1370
|
+
return this.isPredicate();
|
|
1150
1371
|
}
|
|
1151
1372
|
/**
|
|
1152
|
-
* Parse IS
|
|
1373
|
+
* Parse the postfix IS predicates, each a unary postfix operator on the
|
|
1374
|
+
* operand: `IS [NOT] NULL`, `IS [NOT] TRUE`, `IS [NOT] FALSE`. A general
|
|
1375
|
+
* `IS <expr>` (anything other than NULL/TRUE/FALSE) is unsupported — we
|
|
1376
|
+
* backtrack the consumed `IS [NOT]` so the caller surfaces the same error
|
|
1377
|
+
* as before.
|
|
1153
1378
|
*/
|
|
1154
|
-
|
|
1379
|
+
isPredicate() {
|
|
1155
1380
|
const startToken = this.peek();
|
|
1156
1381
|
const expr = this.equality();
|
|
1157
1382
|
if (this.match(TokenType.IS)) {
|
|
@@ -1160,7 +1385,15 @@ export class Parser {
|
|
|
1160
1385
|
const operator = isNot ? 'IS NOT NULL' : 'IS NULL';
|
|
1161
1386
|
return { type: 'unary', operator, expr, loc: _createLoc(startToken, this.previous()) };
|
|
1162
1387
|
}
|
|
1163
|
-
|
|
1388
|
+
if (this.match(TokenType.TRUE)) {
|
|
1389
|
+
const operator = isNot ? 'IS NOT TRUE' : 'IS TRUE';
|
|
1390
|
+
return { type: 'unary', operator, expr, loc: _createLoc(startToken, this.previous()) };
|
|
1391
|
+
}
|
|
1392
|
+
if (this.match(TokenType.FALSE)) {
|
|
1393
|
+
const operator = isNot ? 'IS NOT FALSE' : 'IS FALSE';
|
|
1394
|
+
return { type: 'unary', operator, expr, loc: _createLoc(startToken, this.previous()) };
|
|
1395
|
+
}
|
|
1396
|
+
// IS [NOT] not followed by NULL/TRUE/FALSE — backtrack the IS [NOT].
|
|
1164
1397
|
if (isNot)
|
|
1165
1398
|
this.current--;
|
|
1166
1399
|
this.current--;
|
|
@@ -1193,10 +1426,9 @@ export class Parser {
|
|
|
1193
1426
|
if (this.match(TokenType.IN)) {
|
|
1194
1427
|
// NOT IN
|
|
1195
1428
|
this.consume(TokenType.LPAREN, "Expected '(' after NOT IN.");
|
|
1196
|
-
if (this.
|
|
1197
|
-
// NOT IN subquery: expr NOT IN (
|
|
1198
|
-
const
|
|
1199
|
-
const subquery = this.selectStatement(selectToken);
|
|
1429
|
+
if (this.checkSubqueryStart()) {
|
|
1430
|
+
// NOT IN subquery: expr NOT IN (<QueryExpr>)
|
|
1431
|
+
const subquery = this.parseQueryExpr(undefined, /*requireReturning*/ true);
|
|
1200
1432
|
const endToken = this.consume(TokenType.RPAREN, "Expected ')' after NOT IN subquery.");
|
|
1201
1433
|
// Create an IN expression with subquery, then wrap in NOT
|
|
1202
1434
|
const inExpr = {
|
|
@@ -1306,13 +1538,12 @@ export class Parser {
|
|
|
1306
1538
|
};
|
|
1307
1539
|
}
|
|
1308
1540
|
else if (operatorToken.type === TokenType.IN) {
|
|
1309
|
-
// Parse IN expression: expr IN (value1, value2, ...) or expr IN (
|
|
1541
|
+
// Parse IN expression: expr IN (value1, value2, ...) or expr IN (<QueryExpr>)
|
|
1310
1542
|
this.consume(TokenType.LPAREN, "Expected '(' after IN.");
|
|
1311
1543
|
// Check if this is a subquery or value list
|
|
1312
|
-
if (this.
|
|
1313
|
-
// IN subquery: expr IN (
|
|
1314
|
-
const
|
|
1315
|
-
const subquery = this.selectStatement(selectToken);
|
|
1544
|
+
if (this.checkSubqueryStart()) {
|
|
1545
|
+
// IN subquery: expr IN (<QueryExpr>)
|
|
1546
|
+
const subquery = this.parseQueryExpr(undefined, /*requireReturning*/ true);
|
|
1316
1547
|
const endToken = this.consume(TokenType.RPAREN, "Expected ')' after IN subquery.");
|
|
1317
1548
|
// Create an IN expression with subquery
|
|
1318
1549
|
expr = {
|
|
@@ -1410,7 +1641,9 @@ export class Parser {
|
|
|
1410
1641
|
const expr = this.jsonPath();
|
|
1411
1642
|
if (this.matchKeyword('COLLATE')) {
|
|
1412
1643
|
const collationToken = this.consume(TokenType.IDENTIFIER, "Expected collation name after COLLATE.");
|
|
1413
|
-
|
|
1644
|
+
// getIdentifierValue strips the quotes from a quoted collation name (e.g.
|
|
1645
|
+
// `collate "select"`); using the raw lexeme would embed them in the value.
|
|
1646
|
+
return { type: 'collate', expr, collation: this.getIdentifierValue(collationToken), loc: _createLoc(startToken, collationToken) };
|
|
1414
1647
|
}
|
|
1415
1648
|
return expr;
|
|
1416
1649
|
}
|
|
@@ -1493,12 +1726,11 @@ export class Parser {
|
|
|
1493
1726
|
const endToken = this.consume(TokenType.RPAREN, "Expected ')' after CAST expression type.");
|
|
1494
1727
|
return { type: 'cast', expr, targetType, loc: _createLoc(castToken, endToken) };
|
|
1495
1728
|
}
|
|
1496
|
-
// EXISTS expression: EXISTS(
|
|
1729
|
+
// EXISTS expression: EXISTS(<QueryExpr>)
|
|
1497
1730
|
if (this.match(TokenType.EXISTS)) {
|
|
1498
1731
|
const existsToken = this.previous();
|
|
1499
1732
|
this.consume(TokenType.LPAREN, "Expected '(' after EXISTS.");
|
|
1500
|
-
const
|
|
1501
|
-
const subquery = this.selectStatement(selectToken);
|
|
1733
|
+
const subquery = this.parseQueryExpr(undefined, /*requireReturning*/ true);
|
|
1502
1734
|
const endToken = this.consume(TokenType.RPAREN, "Expected ')' after EXISTS subquery.");
|
|
1503
1735
|
return {
|
|
1504
1736
|
type: 'exists',
|
|
@@ -1558,8 +1790,8 @@ export class Parser {
|
|
|
1558
1790
|
return { type: 'parameter', name: nameToken.lexeme, loc: _createLoc(startToken, nameToken) };
|
|
1559
1791
|
}
|
|
1560
1792
|
// Function call (with optional window function support)
|
|
1561
|
-
if (this.checkIdentifierLike([
|
|
1562
|
-
const name = this.consumeIdentifier([
|
|
1793
|
+
if (this.checkIdentifierLike([...CONTEXTUAL_KEYWORDS, 'replace']) && this.checkNext(1, TokenType.LPAREN)) {
|
|
1794
|
+
const name = this.consumeIdentifier([...CONTEXTUAL_KEYWORDS, 'replace'], "Expected function name.");
|
|
1563
1795
|
this.consume(TokenType.LPAREN, "Expected '(' after function name.");
|
|
1564
1796
|
const args = [];
|
|
1565
1797
|
let distinct = false;
|
|
@@ -1609,16 +1841,15 @@ export class Parser {
|
|
|
1609
1841
|
return funcExpr;
|
|
1610
1842
|
}
|
|
1611
1843
|
// Column/identifier expressions
|
|
1612
|
-
|
|
1613
|
-
if (this.checkIdentifierLike(contextualKeywords)) {
|
|
1844
|
+
if (this.checkIdentifierLike(CONTEXTUAL_KEYWORDS)) {
|
|
1614
1845
|
// Schema.table.column
|
|
1615
|
-
if (this.checkNext(1, TokenType.DOT) && this.checkIdentifierLikeAt(2,
|
|
1616
|
-
this.checkNext(3, TokenType.DOT) && this.checkIdentifierLikeAt(4,
|
|
1617
|
-
const schema = this.consumeIdentifier(
|
|
1846
|
+
if (this.checkNext(1, TokenType.DOT) && this.checkIdentifierLikeAt(2, CONTEXTUAL_KEYWORDS) &&
|
|
1847
|
+
this.checkNext(3, TokenType.DOT) && this.checkIdentifierLikeAt(4, CONTEXTUAL_KEYWORDS)) {
|
|
1848
|
+
const schema = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected schema name.");
|
|
1618
1849
|
this.advance(); // Consume DOT
|
|
1619
|
-
const table = this.consumeIdentifier(
|
|
1850
|
+
const table = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected table name.");
|
|
1620
1851
|
this.advance(); // Consume DOT
|
|
1621
|
-
const name = this.consumeIdentifier(
|
|
1852
|
+
const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name.");
|
|
1622
1853
|
const nameToken = this.previous();
|
|
1623
1854
|
return {
|
|
1624
1855
|
type: 'column',
|
|
@@ -1629,10 +1860,10 @@ export class Parser {
|
|
|
1629
1860
|
};
|
|
1630
1861
|
}
|
|
1631
1862
|
// table.column
|
|
1632
|
-
else if (this.checkNext(1, TokenType.DOT) && this.checkIdentifierLikeAt(2,
|
|
1633
|
-
const table = this.consumeIdentifier(
|
|
1863
|
+
else if (this.checkNext(1, TokenType.DOT) && this.checkIdentifierLikeAt(2, CONTEXTUAL_KEYWORDS)) {
|
|
1864
|
+
const table = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected table name.");
|
|
1634
1865
|
this.advance(); // Consume DOT
|
|
1635
|
-
const name = this.consumeIdentifier(
|
|
1866
|
+
const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name.");
|
|
1636
1867
|
const nameToken = this.previous();
|
|
1637
1868
|
return {
|
|
1638
1869
|
type: 'column',
|
|
@@ -1643,7 +1874,7 @@ export class Parser {
|
|
|
1643
1874
|
}
|
|
1644
1875
|
// just column
|
|
1645
1876
|
else {
|
|
1646
|
-
const name = this.consumeIdentifier(
|
|
1877
|
+
const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name.");
|
|
1647
1878
|
const nameToken = this.previous();
|
|
1648
1879
|
return {
|
|
1649
1880
|
type: 'column',
|
|
@@ -1652,12 +1883,12 @@ export class Parser {
|
|
|
1652
1883
|
};
|
|
1653
1884
|
}
|
|
1654
1885
|
}
|
|
1655
|
-
// Parenthesized expression or scalar subquery
|
|
1886
|
+
// Parenthesized expression or scalar / row subquery.
|
|
1887
|
+
// A leading SELECT/VALUES/WITH/INSERT/UPDATE/DELETE here disambiguates
|
|
1888
|
+
// to a subquery; anything else is a parenthesized scalar expression.
|
|
1656
1889
|
if (this.match(TokenType.LPAREN)) {
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
const selectToken = this.consume(TokenType.SELECT, "Expected 'SELECT' in subquery.");
|
|
1660
|
-
const subquery = this.selectStatement(selectToken);
|
|
1890
|
+
if (this.checkSubqueryStart()) {
|
|
1891
|
+
const subquery = this.parseQueryExpr(undefined, /*requireReturning*/ true);
|
|
1661
1892
|
this.consume(TokenType.RPAREN, "Expected ')' after subquery.");
|
|
1662
1893
|
return {
|
|
1663
1894
|
type: 'subquery',
|
|
@@ -1810,6 +2041,23 @@ export class Parser {
|
|
|
1810
2041
|
return false;
|
|
1811
2042
|
return this.tokens[this.current + n].type === type;
|
|
1812
2043
|
}
|
|
2044
|
+
/**
|
|
2045
|
+
* Non-consuming lookahead for a trailing `with defaults` clause — `WITH`
|
|
2046
|
+
* followed by the contextual `DEFAULTS` keyword (an IDENTIFIER lexeme, since
|
|
2047
|
+
* DEFAULTS is not reserved). Used by the VALUES trailing-clause path to decide
|
|
2048
|
+
* whether to wrap `VALUES …` as `SELECT * FROM (VALUES …)` so the select spine's
|
|
2049
|
+
* {@link parseDefaultsClause} can consume the clause — exactly how a trailing
|
|
2050
|
+
* ORDER BY / LIMIT on VALUES already wraps. A trailing `WITH TAGS` (DDL-level)
|
|
2051
|
+
* never matches, so it stays with the outer DDL parser.
|
|
2052
|
+
*/
|
|
2053
|
+
checkWithDefaults() {
|
|
2054
|
+
if (!this.check(TokenType.WITH))
|
|
2055
|
+
return false;
|
|
2056
|
+
const next = this.tokens[this.current + 1];
|
|
2057
|
+
return next !== undefined
|
|
2058
|
+
&& next.type === TokenType.IDENTIFIER
|
|
2059
|
+
&& next.lexeme.toUpperCase() === 'DEFAULTS';
|
|
2060
|
+
}
|
|
1813
2061
|
advance() {
|
|
1814
2062
|
if (!this.isAtEnd())
|
|
1815
2063
|
this.current++;
|
|
@@ -1870,6 +2118,19 @@ export class Parser {
|
|
|
1870
2118
|
this.check(TokenType.FULL) ||
|
|
1871
2119
|
this.check(TokenType.CROSS);
|
|
1872
2120
|
}
|
|
2121
|
+
/**
|
|
2122
|
+
* True when the current token starts a `QueryExpr` (SELECT, VALUES, WITH,
|
|
2123
|
+
* INSERT, UPDATE, DELETE). Used by callers that have already consumed an
|
|
2124
|
+
* `(` to decide between a subquery and a parenthesized scalar expression.
|
|
2125
|
+
*/
|
|
2126
|
+
checkSubqueryStart() {
|
|
2127
|
+
return this.check(TokenType.SELECT)
|
|
2128
|
+
|| this.check(TokenType.VALUES)
|
|
2129
|
+
|| this.check(TokenType.WITH)
|
|
2130
|
+
|| this.check(TokenType.INSERT)
|
|
2131
|
+
|| this.check(TokenType.UPDATE)
|
|
2132
|
+
|| this.check(TokenType.DELETE);
|
|
2133
|
+
}
|
|
1873
2134
|
isEndOfClause() {
|
|
1874
2135
|
const token = this.peek().type;
|
|
1875
2136
|
return token === TokenType.FROM ||
|
|
@@ -1884,23 +2145,36 @@ export class Parser {
|
|
|
1884
2145
|
}
|
|
1885
2146
|
// --- Statement Parsing Stubs ---
|
|
1886
2147
|
/** @internal */
|
|
1887
|
-
updateStatement(startToken,
|
|
1888
|
-
|
|
1889
|
-
//
|
|
2148
|
+
updateStatement(startToken, withClause) {
|
|
2149
|
+
// Inline subquery target: `update (select …) as v set …`. When present, the
|
|
2150
|
+
// alias is the target's correlation name; mirror it into a synthetic placeholder
|
|
2151
|
+
// `table` (= the alias) so generic `stmt.table.name` reads stay total, and carry
|
|
2152
|
+
// it on `alias`. Otherwise fall through to the ordinary named/CTE target.
|
|
2153
|
+
const targetSource = this.subqueryDmlTarget(withClause);
|
|
2154
|
+
const table = targetSource
|
|
2155
|
+
? { type: 'identifier', name: targetSource.alias, loc: targetSource.loc }
|
|
2156
|
+
: this.tableIdentifier();
|
|
2157
|
+
// Parse mutation context assignments and/or tags if present (either may also
|
|
2158
|
+
// appear trailing, after WHERE, via parseTrailingWithClauses).
|
|
1890
2159
|
let contextValues;
|
|
1891
|
-
|
|
2160
|
+
let tags;
|
|
2161
|
+
while (this.matchKeyword('WITH')) {
|
|
1892
2162
|
if (this.matchKeyword('CONTEXT')) {
|
|
1893
2163
|
contextValues = this.parseContextAssignments();
|
|
1894
2164
|
}
|
|
2165
|
+
else if (this.matchKeyword('TAGS')) {
|
|
2166
|
+
tags = this.parseTags();
|
|
2167
|
+
}
|
|
1895
2168
|
else {
|
|
1896
|
-
// Not a WITH CONTEXT clause, backtrack
|
|
2169
|
+
// Not a WITH CONTEXT / WITH TAGS clause, backtrack
|
|
1897
2170
|
this.current--;
|
|
2171
|
+
break;
|
|
1898
2172
|
}
|
|
1899
2173
|
}
|
|
1900
2174
|
this.consume(TokenType.SET, "Expected 'SET' after table name in UPDATE.");
|
|
1901
2175
|
const assignments = [];
|
|
1902
2176
|
do {
|
|
1903
|
-
const column = this.consumeIdentifier(
|
|
2177
|
+
const column = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in SET clause.");
|
|
1904
2178
|
this.consume(TokenType.EQUAL, "Expected '=' after column name in SET clause.");
|
|
1905
2179
|
const value = this.expression();
|
|
1906
2180
|
assignments.push({ column, value });
|
|
@@ -1917,6 +2191,12 @@ export class Parser {
|
|
|
1917
2191
|
}
|
|
1918
2192
|
contextValues = trailingClauses.contextValues;
|
|
1919
2193
|
}
|
|
2194
|
+
if (trailingClauses.tags) {
|
|
2195
|
+
if (tags) {
|
|
2196
|
+
throw this.error(this.previous(), "Duplicate WITH TAGS clause");
|
|
2197
|
+
}
|
|
2198
|
+
tags = trailingClauses.tags;
|
|
2199
|
+
}
|
|
1920
2200
|
const schemaPath = trailingClauses.schemaPath;
|
|
1921
2201
|
// Parse RETURNING clause if present
|
|
1922
2202
|
let returning;
|
|
@@ -1924,21 +2204,33 @@ export class Parser {
|
|
|
1924
2204
|
returning = this.columnList();
|
|
1925
2205
|
}
|
|
1926
2206
|
const endToken = this.previous();
|
|
1927
|
-
return { type: 'update', table, assignments, where, returning, contextValues, schemaPath, loc: _createLoc(startToken, endToken) };
|
|
2207
|
+
return { type: 'update', table, targetSource, alias: targetSource?.alias, assignments, where, returning, contextValues, schemaPath, tags, loc: _createLoc(startToken, endToken) };
|
|
1928
2208
|
}
|
|
1929
2209
|
/** @internal */
|
|
1930
|
-
deleteStatement(startToken,
|
|
2210
|
+
deleteStatement(startToken, withClause) {
|
|
2211
|
+
// `delete` consumes an optional leading `FROM`; the inline-subquery target check
|
|
2212
|
+
// runs AFTER it so `delete from (select …) as v` works (and `delete (select …) as
|
|
2213
|
+
// v` too, matching the optional-FROM behavior). See updateStatement.
|
|
1931
2214
|
this.matchKeyword('FROM');
|
|
1932
|
-
const
|
|
1933
|
-
|
|
2215
|
+
const targetSource = this.subqueryDmlTarget(withClause);
|
|
2216
|
+
const table = targetSource
|
|
2217
|
+
? { type: 'identifier', name: targetSource.alias, loc: targetSource.loc }
|
|
2218
|
+
: this.tableIdentifier();
|
|
2219
|
+
// Parse mutation context assignments and/or tags if present (either may also
|
|
2220
|
+
// appear trailing, after WHERE, via parseTrailingWithClauses).
|
|
1934
2221
|
let contextValues;
|
|
1935
|
-
|
|
2222
|
+
let tags;
|
|
2223
|
+
while (this.matchKeyword('WITH')) {
|
|
1936
2224
|
if (this.matchKeyword('CONTEXT')) {
|
|
1937
2225
|
contextValues = this.parseContextAssignments();
|
|
1938
2226
|
}
|
|
2227
|
+
else if (this.matchKeyword('TAGS')) {
|
|
2228
|
+
tags = this.parseTags();
|
|
2229
|
+
}
|
|
1939
2230
|
else {
|
|
1940
|
-
// Not a WITH CONTEXT clause, backtrack
|
|
2231
|
+
// Not a WITH CONTEXT / WITH TAGS clause, backtrack
|
|
1941
2232
|
this.current--;
|
|
2233
|
+
break;
|
|
1942
2234
|
}
|
|
1943
2235
|
}
|
|
1944
2236
|
let where;
|
|
@@ -1953,6 +2245,12 @@ export class Parser {
|
|
|
1953
2245
|
}
|
|
1954
2246
|
contextValues = trailingClauses.contextValues;
|
|
1955
2247
|
}
|
|
2248
|
+
if (trailingClauses.tags) {
|
|
2249
|
+
if (tags) {
|
|
2250
|
+
throw this.error(this.previous(), "Duplicate WITH TAGS clause");
|
|
2251
|
+
}
|
|
2252
|
+
tags = trailingClauses.tags;
|
|
2253
|
+
}
|
|
1956
2254
|
const schemaPath = trailingClauses.schemaPath;
|
|
1957
2255
|
// Parse RETURNING clause if present
|
|
1958
2256
|
let returning;
|
|
@@ -1960,7 +2258,7 @@ export class Parser {
|
|
|
1960
2258
|
returning = this.columnList();
|
|
1961
2259
|
}
|
|
1962
2260
|
const endToken = this.previous();
|
|
1963
|
-
return { type: 'delete', table, where, returning, contextValues, schemaPath, loc: _createLoc(startToken, endToken) };
|
|
2261
|
+
return { type: 'delete', table, targetSource, alias: targetSource?.alias, where, returning, contextValues, schemaPath, tags, loc: _createLoc(startToken, endToken) };
|
|
1964
2262
|
}
|
|
1965
2263
|
/** @internal */
|
|
1966
2264
|
valuesStatement(startToken) {
|
|
@@ -1979,23 +2277,85 @@ export class Parser {
|
|
|
1979
2277
|
const endToken = this.previous();
|
|
1980
2278
|
return { type: 'values', values, loc: _createLoc(startToken, endToken) };
|
|
1981
2279
|
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Parses VALUES at a position that also accepts trailing compound
|
|
2282
|
+
* (UNION / INTERSECT / EXCEPT / DIFF) and — outside compound-leg
|
|
2283
|
+
* position — trailing ORDER BY / LIMIT / OFFSET. Used at the top-level
|
|
2284
|
+
* statement dispatch, inside `parseQueryExpr`, and as a compound right
|
|
2285
|
+
* leg so that chains like `VALUES (1) UNION VALUES (2) UNION VALUES (3)`
|
|
2286
|
+
* and `VALUES (1) ORDER BY 1 LIMIT 2` parse uniformly.
|
|
2287
|
+
*
|
|
2288
|
+
* Implementation note: the AST `compound` / `orderBy` / `limit` fields
|
|
2289
|
+
* live on `SelectStmt`, not on `ValuesStmt`. When any of those clauses
|
|
2290
|
+
* follow VALUES we synthesize a `SELECT * FROM (VALUES …)` wrapper so the
|
|
2291
|
+
* existing SELECT machinery applies. The wrapper is structurally
|
|
2292
|
+
* indistinguishable from what a user-written `SELECT * FROM (VALUES …)`
|
|
2293
|
+
* would produce.
|
|
2294
|
+
*
|
|
2295
|
+
* `isCompoundSubquery` suppresses ORDER BY / LIMIT consumption — those
|
|
2296
|
+
* belong to the outer compound when VALUES appears as a right leg.
|
|
2297
|
+
*/
|
|
2298
|
+
valuesStatementWithOptionalCompound(startToken, withClause, isCompoundSubquery = false) {
|
|
2299
|
+
const values = this.valuesStatement(startToken);
|
|
2300
|
+
const hasCompound = this.checkCompoundOperator();
|
|
2301
|
+
// A trailing `with defaults (…)` wraps just like ORDER BY / LIMIT so the
|
|
2302
|
+
// select spine consumes it — a VALUES-bodied view's defaults are inert
|
|
2303
|
+
// (the view is non-updateable), but the clause must still parse + store.
|
|
2304
|
+
const hasTrailing = !isCompoundSubquery && (this.check(TokenType.ORDER) || this.check(TokenType.LIMIT) || this.checkWithDefaults());
|
|
2305
|
+
if (!hasCompound && !hasTrailing) {
|
|
2306
|
+
return values;
|
|
2307
|
+
}
|
|
2308
|
+
// Wrap as `SELECT * FROM (<values>) AS <synthetic alias>` and continue
|
|
2309
|
+
// parsing as a SELECT so the trailing clauses fold in naturally.
|
|
2310
|
+
const wrapped = this.wrapAsSubquerySelect(values, startToken);
|
|
2311
|
+
return this.continueSelectAfterFrom(wrapped, withClause, startToken, isCompoundSubquery);
|
|
2312
|
+
}
|
|
2313
|
+
/**
|
|
2314
|
+
* Picks up an in-progress SELECT after its FROM clause is already populated
|
|
2315
|
+
* and parses any remaining trailing clauses by delegating to the shared
|
|
2316
|
+
* compound-tail / ORDER-LIMIT parsers. Used by
|
|
2317
|
+
* `valuesStatementWithOptionalCompound` to graft a compound chain and
|
|
2318
|
+
* trailing clauses onto a synthesized SELECT-from-VALUES wrapper. The
|
|
2319
|
+
* synthesized wrapper never carries its own WHERE / GROUP BY / HAVING — bare
|
|
2320
|
+
* VALUES at top level does not accept those clauses, so they fall through as
|
|
2321
|
+
* a statement-boundary parse error rather than being silently absorbed.
|
|
2322
|
+
*
|
|
2323
|
+
* `startToken` seeds the synthetic alias of any further subquery wrapper the
|
|
2324
|
+
* tail parser mints; `isCompoundSubquery` suppresses ORDER BY / LIMIT
|
|
2325
|
+
* consumption — those belong to the outer compound when this wrapper is a
|
|
2326
|
+
* right leg.
|
|
2327
|
+
*/
|
|
2328
|
+
continueSelectAfterFrom(sel, withClause, startToken, isCompoundSubquery = false) {
|
|
2329
|
+
// Compound chain (left-associative tail) binds before ORDER BY / LIMIT.
|
|
2330
|
+
const result = this.parseCompoundTail(sel, withClause, startToken);
|
|
2331
|
+
this.parseTrailingOrderLimit(result, isCompoundSubquery);
|
|
2332
|
+
// Trailing `with defaults (…)` binds to the whole compound (see selectStatement).
|
|
2333
|
+
if (!isCompoundSubquery) {
|
|
2334
|
+
const defaults = this.parseDefaultsClause();
|
|
2335
|
+
if (defaults)
|
|
2336
|
+
result.defaults = defaults;
|
|
2337
|
+
}
|
|
2338
|
+
return result;
|
|
2339
|
+
}
|
|
1982
2340
|
/** @internal */
|
|
1983
2341
|
createStatement(startToken, withClause) {
|
|
1984
|
-
|
|
2342
|
+
// TEMP/TEMPORARY is not a Quereus concept — the schema is already transient
|
|
2343
|
+
// and temp placement was never wired. Reject it rather than silently ignore.
|
|
1985
2344
|
if (this.peekKeyword('TEMP') || this.peekKeyword('TEMPORARY')) {
|
|
1986
|
-
|
|
1987
|
-
this.advance();
|
|
2345
|
+
throw this.error(this.peek(), "TEMP/TEMPORARY is not supported.");
|
|
1988
2346
|
}
|
|
1989
2347
|
if (this.peekKeyword('TABLE')) {
|
|
1990
2348
|
this.consumeKeyword('TABLE', "Expected 'TABLE' after CREATE.");
|
|
1991
|
-
return this.createTableStatement(startToken,
|
|
2349
|
+
return this.createTableStatement(startToken, withClause);
|
|
1992
2350
|
}
|
|
1993
2351
|
else if (this.peekKeyword('VIEW')) {
|
|
1994
2352
|
this.consumeKeyword('VIEW', "Expected 'VIEW' after CREATE.");
|
|
1995
|
-
return this.createViewStatement(startToken,
|
|
2353
|
+
return this.createViewStatement(startToken, withClause);
|
|
1996
2354
|
}
|
|
1997
|
-
else if (
|
|
1998
|
-
|
|
2355
|
+
else if (this.peekKeyword('MATERIALIZED')) {
|
|
2356
|
+
this.consumeKeyword('MATERIALIZED', "Expected 'MATERIALIZED' after CREATE.");
|
|
2357
|
+
this.consumeKeyword('VIEW', "Expected 'VIEW' after CREATE MATERIALIZED.");
|
|
2358
|
+
return this.createMaterializedViewStatement(startToken, withClause);
|
|
1999
2359
|
}
|
|
2000
2360
|
else if (this.peekKeyword('INDEX')) {
|
|
2001
2361
|
this.consumeKeyword('INDEX', "Expected 'INDEX' after CREATE.");
|
|
@@ -2010,13 +2370,13 @@ export class Parser {
|
|
|
2010
2370
|
this.consumeKeyword('INDEX', "Expected 'INDEX' after CREATE UNIQUE.");
|
|
2011
2371
|
return this.createIndexStatement(startToken, true, withClause);
|
|
2012
2372
|
}
|
|
2013
|
-
throw this.error(this.peek(), "Expected TABLE, [UNIQUE] INDEX, VIEW, ASSERTION, or VIRTUAL after CREATE.");
|
|
2373
|
+
throw this.error(this.peek(), "Expected TABLE, [UNIQUE] INDEX, VIEW, MATERIALIZED VIEW, ASSERTION, or VIRTUAL after CREATE.");
|
|
2014
2374
|
}
|
|
2015
2375
|
/**
|
|
2016
2376
|
* Parse CREATE TABLE statement
|
|
2017
2377
|
* @returns AST for CREATE TABLE
|
|
2018
2378
|
*/
|
|
2019
|
-
createTableStatement(startToken,
|
|
2379
|
+
createTableStatement(startToken, _withClause) {
|
|
2020
2380
|
let ifNotExists = false;
|
|
2021
2381
|
if (this.matchKeyword('IF')) {
|
|
2022
2382
|
this.consumeKeyword('NOT', "Expected 'NOT' after 'IF'.");
|
|
@@ -2040,8 +2400,7 @@ export class Parser {
|
|
|
2040
2400
|
// If we didn't see a comma and the next token looks like the start of another
|
|
2041
2401
|
// column or table constraint, provide a clearer error about a missing comma.
|
|
2042
2402
|
if (!this.check(TokenType.RPAREN)) {
|
|
2043
|
-
const
|
|
2044
|
-
const nextLooksLikeAnotherItem = this.peekKeyword('PRIMARY') || this.peekKeyword('UNIQUE') || this.peekKeyword('CHECK') || this.peekKeyword('FOREIGN') || this.peekKeyword('CONSTRAINT') || this.checkIdentifierLike(contextualKeywords);
|
|
2403
|
+
const nextLooksLikeAnotherItem = this.peekKeyword('PRIMARY') || this.peekKeyword('UNIQUE') || this.peekKeyword('CHECK') || this.peekKeyword('FOREIGN') || this.peekKeyword('CONSTRAINT') || this.checkIdentifierLike(CONTEXTUAL_KEYWORDS);
|
|
2045
2404
|
if (nextLooksLikeAnotherItem) {
|
|
2046
2405
|
const next = this.peek();
|
|
2047
2406
|
throw this.error(next, `Expected ',' between table elements. Did you forget a comma before '${next.lexeme}'?`);
|
|
@@ -2085,6 +2444,11 @@ export class Parser {
|
|
|
2085
2444
|
this.consume(TokenType.RPAREN, "Expected ')' after module arguments.");
|
|
2086
2445
|
}
|
|
2087
2446
|
}
|
|
2447
|
+
// Optional `maintained as <body … with defaults (…)>` clause — the declared-shape
|
|
2448
|
+
// maintained-table form. `maintained` is contextual (an IDENTIFIER lexeme match), and
|
|
2449
|
+
// this position (after the column list / USING clause, before the WITH clauses) is
|
|
2450
|
+
// unambiguous, so no look-ahead guard is needed.
|
|
2451
|
+
const maintained = this.parseMaintainedClause();
|
|
2088
2452
|
// Parse trailing WITH clauses (CONTEXT, TAGS) in any order
|
|
2089
2453
|
let contextDefinitions;
|
|
2090
2454
|
let tags;
|
|
@@ -2113,14 +2477,57 @@ export class Parser {
|
|
|
2113
2477
|
ifNotExists,
|
|
2114
2478
|
columns,
|
|
2115
2479
|
constraints,
|
|
2116
|
-
isTemporary,
|
|
2117
2480
|
moduleName,
|
|
2118
2481
|
moduleArgs,
|
|
2119
2482
|
contextDefinitions,
|
|
2120
2483
|
tags,
|
|
2484
|
+
maintained,
|
|
2121
2485
|
loc: _createLoc(startToken, this.previous()),
|
|
2122
2486
|
};
|
|
2123
2487
|
}
|
|
2488
|
+
/**
|
|
2489
|
+
* Parse the optional `maintained as <query-expr>` clause of a CREATE TABLE (or
|
|
2490
|
+
* a `declare schema` table item) — the declared-shape maintained-table form.
|
|
2491
|
+
* Any omitted-insert defaults ride inside the body select's trailing
|
|
2492
|
+
* `with defaults (…)` clause. Returns undefined when the clause is absent.
|
|
2493
|
+
*/
|
|
2494
|
+
parseMaintainedClause() {
|
|
2495
|
+
if (!this.matchKeyword('MAINTAINED'))
|
|
2496
|
+
return undefined;
|
|
2497
|
+
// Optional explicit output-column rename list before AS — the lossless
|
|
2498
|
+
// table-form encoding of the MV-sugar `(a, b)` renames. Absent ⇒ implicit
|
|
2499
|
+
// (the body follows its source shape on reopen). The required AS that
|
|
2500
|
+
// follows keeps this unambiguous against a parenthesized body.
|
|
2501
|
+
const columns = this.parseMaintainedColumnList();
|
|
2502
|
+
this.consumeKeyword('AS', "Expected 'AS' after MAINTAINED.");
|
|
2503
|
+
// Body is any QueryExpr — bare SELECT / VALUES / WITH … SELECT all qualify.
|
|
2504
|
+
// DML bodies parse here but the planner rejects them. A trailing
|
|
2505
|
+
// `with defaults (…)` is consumed by the select spine into `select.defaults`.
|
|
2506
|
+
const select = this.parseQueryExpr(undefined, /*requireReturning*/ true);
|
|
2507
|
+
return { columns, select };
|
|
2508
|
+
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Parse the optional parenthesized output-column rename list that may precede the
|
|
2511
|
+
* `AS` of a maintained-table form — shared by `create table … maintained (cols) as`
|
|
2512
|
+
* and `alter table … set maintained (cols) as`. Returns undefined when absent
|
|
2513
|
+
* (the implicit form). An empty `()` is rejected: it could not round-trip
|
|
2514
|
+
* (`maintainedClauseToString` drops an empty list) and the clause's absence is
|
|
2515
|
+
* already the implicit signal, so require at least one column.
|
|
2516
|
+
*/
|
|
2517
|
+
parseMaintainedColumnList() {
|
|
2518
|
+
if (!this.check(TokenType.LPAREN))
|
|
2519
|
+
return undefined;
|
|
2520
|
+
this.consume(TokenType.LPAREN, "Expected '(' to start maintained column list.");
|
|
2521
|
+
if (this.check(TokenType.RPAREN)) {
|
|
2522
|
+
throw this.error(this.peek(), "Expected at least one column name in the maintained column list.");
|
|
2523
|
+
}
|
|
2524
|
+
const columns = [];
|
|
2525
|
+
do {
|
|
2526
|
+
columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in maintained column list."));
|
|
2527
|
+
} while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
|
|
2528
|
+
this.consume(TokenType.RPAREN, "Expected ')' after maintained column list.");
|
|
2529
|
+
return columns;
|
|
2530
|
+
}
|
|
2124
2531
|
/**
|
|
2125
2532
|
* Parse CREATE INDEX statement
|
|
2126
2533
|
* @param isUnique Flag indicating if UNIQUE keyword was already parsed
|
|
@@ -2173,7 +2580,7 @@ export class Parser {
|
|
|
2173
2580
|
* Parse CREATE VIEW statement
|
|
2174
2581
|
* @returns AST for CREATE VIEW
|
|
2175
2582
|
*/
|
|
2176
|
-
createViewStatement(startToken,
|
|
2583
|
+
createViewStatement(startToken, withClause) {
|
|
2177
2584
|
let ifNotExists = false;
|
|
2178
2585
|
if (this.matchKeyword('IF')) {
|
|
2179
2586
|
this.consumeKeyword('NOT', "Expected 'NOT' after 'IF'.");
|
|
@@ -2185,29 +2592,20 @@ export class Parser {
|
|
|
2185
2592
|
if (this.check(TokenType.LPAREN)) {
|
|
2186
2593
|
this.consume(TokenType.LPAREN, "Expected '(' to start view column list.");
|
|
2187
2594
|
columns = [];
|
|
2188
|
-
const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
|
|
2189
2595
|
if (!this.check(TokenType.RPAREN)) {
|
|
2190
2596
|
do {
|
|
2191
|
-
columns.push(this.consumeIdentifier(
|
|
2597
|
+
columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in view column list."));
|
|
2192
2598
|
} while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
|
|
2193
2599
|
}
|
|
2194
2600
|
this.consume(TokenType.RPAREN, "Expected ')' after view column list.");
|
|
2195
2601
|
}
|
|
2196
|
-
this.consumeKeyword('AS', "Expected 'AS' before
|
|
2197
|
-
// CREATE VIEW body
|
|
2198
|
-
//
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
const selectStartToken = this.consume(TokenType.SELECT, "Expected 'SELECT' after 'AS' in CREATE VIEW.");
|
|
2204
|
-
const select = this.selectStatement(selectStartToken, innerWith ?? withClause);
|
|
2205
|
-
if (innerWith) {
|
|
2206
|
-
select.withClause = innerWith;
|
|
2207
|
-
if (innerWith.loc && select.loc) {
|
|
2208
|
-
select.loc.start = innerWith.loc.start;
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2602
|
+
this.consumeKeyword('AS', "Expected 'AS' before view body in CREATE VIEW.");
|
|
2603
|
+
// CREATE VIEW body is any QueryExpr — bare SELECT / VALUES / WITH …
|
|
2604
|
+
// SELECT all qualify. DML bodies parse here but the planner rejects
|
|
2605
|
+
// them (mutating views are out of scope for this milestone).
|
|
2606
|
+
const select = this.parseQueryExpr(withClause, /*requireReturning*/ true);
|
|
2607
|
+
// A trailing `with defaults (…)` rides inside the select body
|
|
2608
|
+
// (`select.defaults`); only the DDL-level WITH TAGS is parsed here.
|
|
2211
2609
|
// Parse optional WITH TAGS
|
|
2212
2610
|
let tags;
|
|
2213
2611
|
if (this.matchKeyword('WITH')) {
|
|
@@ -2224,11 +2622,195 @@ export class Parser {
|
|
|
2224
2622
|
ifNotExists,
|
|
2225
2623
|
columns,
|
|
2226
2624
|
select,
|
|
2227
|
-
isTemporary,
|
|
2228
2625
|
tags,
|
|
2229
2626
|
loc: _createLoc(startToken, this.previous()),
|
|
2230
2627
|
};
|
|
2231
2628
|
}
|
|
2629
|
+
/**
|
|
2630
|
+
* Parse the optional trailing `with defaults ( col = expr , … )` clause of a
|
|
2631
|
+
* core select — per-column omitted-insert defaults for view write-through.
|
|
2632
|
+
* Modeled byte-for-byte on {@link parseInverseClause}: commits only once
|
|
2633
|
+
* DEFAULTS follows WITH, so a statement-trailing `with tags` / `with schema` /
|
|
2634
|
+
* `with context` stays with the outer parser (the rewound token is WITH, which
|
|
2635
|
+
* never touches the parenStack — so the bare cursor rewind is safe). Returns
|
|
2636
|
+
* undefined when the clause is absent. An empty assignment list is a parse error.
|
|
2637
|
+
*/
|
|
2638
|
+
parseDefaultsClause() {
|
|
2639
|
+
if (!this.check(TokenType.WITH))
|
|
2640
|
+
return undefined;
|
|
2641
|
+
this.advance();
|
|
2642
|
+
if (!this.peekKeyword('DEFAULTS')) {
|
|
2643
|
+
// Not our clause — back up the WITH and stop. Bare cursor rewind is safe
|
|
2644
|
+
// only because the rewound token is WITH: advance() has one non-cursor
|
|
2645
|
+
// side effect (LPAREN/RPAREN parenStack maintenance), which WITH never hits.
|
|
2646
|
+
this.current--;
|
|
2647
|
+
return undefined;
|
|
2648
|
+
}
|
|
2649
|
+
this.advance();
|
|
2650
|
+
this.consume(TokenType.LPAREN, "Expected '(' after WITH DEFAULTS.");
|
|
2651
|
+
const defaults = [];
|
|
2652
|
+
const seen = new Set();
|
|
2653
|
+
do {
|
|
2654
|
+
const columnToken = this.peek();
|
|
2655
|
+
const column = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in WITH DEFAULTS.");
|
|
2656
|
+
if (seen.has(column.toLowerCase())) {
|
|
2657
|
+
throw this.error(columnToken, `Duplicate column '${column}' in WITH DEFAULTS.`);
|
|
2658
|
+
}
|
|
2659
|
+
seen.add(column.toLowerCase());
|
|
2660
|
+
this.consume(TokenType.EQUAL, `Expected '=' after WITH DEFAULTS column '${column}'.`);
|
|
2661
|
+
const expr = this.expression();
|
|
2662
|
+
defaults.push({ column, expr });
|
|
2663
|
+
} while (this.match(TokenType.COMMA));
|
|
2664
|
+
this.consume(TokenType.RPAREN, "Expected ')' after WITH DEFAULTS list.");
|
|
2665
|
+
return defaults;
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2668
|
+
* Parse the optional `with inverse ( col = expr , … )` clause trailing an
|
|
2669
|
+
* expression result column — authored write-back expressions for view
|
|
2670
|
+
* write-through (inert metadata until the write path consumes it; validation
|
|
2671
|
+
* is build-time). Returns undefined when the clause is absent. `inverse` is
|
|
2672
|
+
* contextual: commits only once INVERSE follows WITH, so a statement-trailing
|
|
2673
|
+
* `with schema` / `with context` / `with tags` after the column list stays
|
|
2674
|
+
* with the outer parser. An empty assignment list is a parse error.
|
|
2675
|
+
*/
|
|
2676
|
+
parseInverseClause() {
|
|
2677
|
+
if (!this.check(TokenType.WITH))
|
|
2678
|
+
return undefined;
|
|
2679
|
+
this.advance();
|
|
2680
|
+
if (!this.peekKeyword('INVERSE')) {
|
|
2681
|
+
// Not our clause — back up the WITH and stop. Bare cursor rewind is safe
|
|
2682
|
+
// only because the rewound token is WITH: advance() has one non-cursor
|
|
2683
|
+
// side effect (LPAREN/RPAREN parenStack maintenance), which WITH never hits.
|
|
2684
|
+
this.current--;
|
|
2685
|
+
return undefined;
|
|
2686
|
+
}
|
|
2687
|
+
this.advance();
|
|
2688
|
+
this.consume(TokenType.LPAREN, "Expected '(' after WITH INVERSE.");
|
|
2689
|
+
const assignments = [];
|
|
2690
|
+
const seen = new Set();
|
|
2691
|
+
do {
|
|
2692
|
+
const columnToken = this.peek();
|
|
2693
|
+
const column = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in WITH INVERSE.");
|
|
2694
|
+
if (seen.has(column.toLowerCase())) {
|
|
2695
|
+
throw this.error(columnToken, `Duplicate column '${column}' in WITH INVERSE.`);
|
|
2696
|
+
}
|
|
2697
|
+
seen.add(column.toLowerCase());
|
|
2698
|
+
this.consume(TokenType.EQUAL, `Expected '=' after WITH INVERSE column '${column}'.`);
|
|
2699
|
+
const expr = this.expression();
|
|
2700
|
+
assignments.push({ column, expr });
|
|
2701
|
+
} while (this.match(TokenType.COMMA));
|
|
2702
|
+
this.consume(TokenType.RPAREN, "Expected ')' after WITH INVERSE list.");
|
|
2703
|
+
return assignments;
|
|
2704
|
+
}
|
|
2705
|
+
/**
|
|
2706
|
+
* A `*` / `t.*` result column cannot carry WITH INVERSE (it names no single
|
|
2707
|
+
* expression to invert). Name the clause in the diagnostic rather than letting
|
|
2708
|
+
* the leftover WITH garble into a downstream CTE/clause error. A trailing
|
|
2709
|
+
* `with schema` / `with tags` is untouched — parseInverseClause commits only
|
|
2710
|
+
* on WITH followed by INVERSE.
|
|
2711
|
+
*/
|
|
2712
|
+
rejectInverseClauseOnStar() {
|
|
2713
|
+
const withToken = this.peek();
|
|
2714
|
+
if (this.parseInverseClause()) {
|
|
2715
|
+
throw this.error(withToken, "WITH INVERSE cannot apply to a '*' result column.");
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
/**
|
|
2719
|
+
* Parse CREATE MATERIALIZED VIEW statement.
|
|
2720
|
+
*
|
|
2721
|
+
* Syntax: `create materialized view <name> [(cols)] [using <module>(args)] as <query-expr> [with tags ...]`.
|
|
2722
|
+
* The optional `using` clause is parsed before `as` to stay unambiguous with the query body;
|
|
2723
|
+
* v1 restricts the backing module to `memory` at build time (the AST keeps the slot forward-compatible).
|
|
2724
|
+
*/
|
|
2725
|
+
createMaterializedViewStatement(startToken, withClause) {
|
|
2726
|
+
let ifNotExists = false;
|
|
2727
|
+
if (this.matchKeyword('IF')) {
|
|
2728
|
+
this.consumeKeyword('NOT', "Expected 'NOT' after 'IF'.");
|
|
2729
|
+
this.consumeKeyword('EXISTS', "Expected 'EXISTS' after 'IF NOT'.");
|
|
2730
|
+
ifNotExists = true;
|
|
2731
|
+
}
|
|
2732
|
+
const view = this.tableIdentifier();
|
|
2733
|
+
let columns;
|
|
2734
|
+
if (this.check(TokenType.LPAREN)) {
|
|
2735
|
+
this.consume(TokenType.LPAREN, "Expected '(' to start view column list.");
|
|
2736
|
+
columns = [];
|
|
2737
|
+
if (!this.check(TokenType.RPAREN)) {
|
|
2738
|
+
do {
|
|
2739
|
+
columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in view column list."));
|
|
2740
|
+
} while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
|
|
2741
|
+
}
|
|
2742
|
+
this.consume(TokenType.RPAREN, "Expected ')' after view column list.");
|
|
2743
|
+
}
|
|
2744
|
+
// Optional backing-module clause (`using mem(...)`) before the body.
|
|
2745
|
+
let moduleName;
|
|
2746
|
+
const moduleArgs = {};
|
|
2747
|
+
if (this.matchKeyword('USING')) {
|
|
2748
|
+
moduleName = this.consumeIdentifier("Expected module name after 'USING'.");
|
|
2749
|
+
if (this.match(TokenType.LPAREN)) {
|
|
2750
|
+
let positionalIndex = 0;
|
|
2751
|
+
if (!this.check(TokenType.RPAREN)) {
|
|
2752
|
+
do {
|
|
2753
|
+
if (this.check(TokenType.STRING) || this.check(TokenType.INTEGER) || this.check(TokenType.FLOAT)) {
|
|
2754
|
+
const token = this.advance();
|
|
2755
|
+
moduleArgs[String(positionalIndex++)] = token.literal;
|
|
2756
|
+
}
|
|
2757
|
+
else if (this.check(TokenType.IDENTIFIER)) {
|
|
2758
|
+
const nameValue = this.nameValueItem('module argument');
|
|
2759
|
+
moduleArgs[nameValue.name] = nameValue.value && nameValue.value.type === 'literal'
|
|
2760
|
+
? getSyncLiteral(nameValue.value)
|
|
2761
|
+
: (nameValue.value && nameValue.value.type === 'identifier' ? nameValue.value.name : nameValue.name);
|
|
2762
|
+
}
|
|
2763
|
+
else {
|
|
2764
|
+
throw this.error(this.peek(), "Expected module argument (string, number, or name=value pair).");
|
|
2765
|
+
}
|
|
2766
|
+
} while (this.match(TokenType.COMMA));
|
|
2767
|
+
}
|
|
2768
|
+
this.consume(TokenType.RPAREN, "Expected ')' after module arguments.");
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
this.consumeKeyword('AS', "Expected 'AS' before view body in CREATE MATERIALIZED VIEW.");
|
|
2772
|
+
// Body is any QueryExpr — bare SELECT / VALUES / WITH … SELECT all qualify.
|
|
2773
|
+
// DML bodies parse here but the planner rejects them.
|
|
2774
|
+
const select = this.parseQueryExpr(withClause, /*requireReturning*/ true);
|
|
2775
|
+
// A trailing `with defaults (…)` rides inside the select body
|
|
2776
|
+
// (`select.defaults`); only the DDL-level WITH TAGS is parsed here.
|
|
2777
|
+
// Parse the trailing `with tags (...)` metadata clause.
|
|
2778
|
+
let tags;
|
|
2779
|
+
while (this.matchKeyword('WITH')) {
|
|
2780
|
+
if (this.matchKeyword('TAGS')) {
|
|
2781
|
+
tags = this.parseTags();
|
|
2782
|
+
}
|
|
2783
|
+
else {
|
|
2784
|
+
this.current--; // Not a clause we own — back up the WITH and stop.
|
|
2785
|
+
break;
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
return {
|
|
2789
|
+
type: 'createMaterializedView',
|
|
2790
|
+
view,
|
|
2791
|
+
ifNotExists,
|
|
2792
|
+
columns,
|
|
2793
|
+
select,
|
|
2794
|
+
moduleName,
|
|
2795
|
+
moduleArgs: moduleName && Object.keys(moduleArgs).length > 0 ? moduleArgs : undefined,
|
|
2796
|
+
tags,
|
|
2797
|
+
loc: _createLoc(startToken, this.previous()),
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
/**
|
|
2801
|
+
* Parse REFRESH MATERIALIZED VIEW statement.
|
|
2802
|
+
* Syntax: `refresh materialized view <name>`.
|
|
2803
|
+
*/
|
|
2804
|
+
refreshStatement(startToken, _withClause) {
|
|
2805
|
+
this.consumeKeyword('MATERIALIZED', "Expected 'MATERIALIZED' after REFRESH.");
|
|
2806
|
+
this.consumeKeyword('VIEW', "Expected 'VIEW' after REFRESH MATERIALIZED.");
|
|
2807
|
+
const name = this.tableIdentifier();
|
|
2808
|
+
return {
|
|
2809
|
+
type: 'refreshMaterializedView',
|
|
2810
|
+
name,
|
|
2811
|
+
loc: _createLoc(startToken, this.previous()),
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2232
2814
|
/**
|
|
2233
2815
|
* Parse CREATE ASSERTION statement
|
|
2234
2816
|
* @returns AST for CREATE ASSERTION
|
|
@@ -2256,6 +2838,11 @@ export class Parser {
|
|
|
2256
2838
|
this.consumeKeyword('TABLE', "Expected TABLE after DROP.");
|
|
2257
2839
|
objectType = 'table';
|
|
2258
2840
|
}
|
|
2841
|
+
else if (this.peekKeyword('MATERIALIZED')) {
|
|
2842
|
+
this.consumeKeyword('MATERIALIZED', "Expected MATERIALIZED after DROP.");
|
|
2843
|
+
this.consumeKeyword('VIEW', "Expected VIEW after DROP MATERIALIZED.");
|
|
2844
|
+
objectType = 'materializedView';
|
|
2845
|
+
}
|
|
2259
2846
|
else if (this.peekKeyword('VIEW')) {
|
|
2260
2847
|
this.consumeKeyword('VIEW', "Expected VIEW after DROP.");
|
|
2261
2848
|
objectType = 'view';
|
|
@@ -2269,7 +2856,7 @@ export class Parser {
|
|
|
2269
2856
|
objectType = 'assertion';
|
|
2270
2857
|
}
|
|
2271
2858
|
else {
|
|
2272
|
-
throw this.error(this.peek(), "Expected TABLE, VIEW, INDEX, or ASSERTION after DROP.");
|
|
2859
|
+
throw this.error(this.peek(), "Expected TABLE, VIEW, MATERIALIZED VIEW, INDEX, or ASSERTION after DROP.");
|
|
2273
2860
|
}
|
|
2274
2861
|
let ifExists = false;
|
|
2275
2862
|
if (this.matchKeyword('IF')) {
|
|
@@ -2285,6 +2872,74 @@ export class Parser {
|
|
|
2285
2872
|
loc: _createLoc(startToken, this.previous()),
|
|
2286
2873
|
};
|
|
2287
2874
|
}
|
|
2875
|
+
/**
|
|
2876
|
+
* Top-level ALTER dispatch. `ALTER` has already been consumed. Branches on the
|
|
2877
|
+
* object keyword: TABLE → the full ALTER TABLE grammar; VIEW / MATERIALIZED VIEW
|
|
2878
|
+
* / INDEX → the v1 `SET TAGS`-only metadata grammar. MATERIALIZED is checked
|
|
2879
|
+
* before VIEW so `MATERIALIZED VIEW` is not mis-parsed as a plain view.
|
|
2880
|
+
*/
|
|
2881
|
+
alterStatement(startToken, withClause) {
|
|
2882
|
+
if (this.peekKeyword('TABLE')) {
|
|
2883
|
+
return this.alterTableStatement(startToken, withClause);
|
|
2884
|
+
}
|
|
2885
|
+
if (this.peekKeyword('MATERIALIZED')) {
|
|
2886
|
+
return this.alterMaterializedViewStatement(startToken);
|
|
2887
|
+
}
|
|
2888
|
+
if (this.peekKeyword('VIEW')) {
|
|
2889
|
+
return this.alterViewStatement(startToken);
|
|
2890
|
+
}
|
|
2891
|
+
if (this.peekKeyword('INDEX')) {
|
|
2892
|
+
return this.alterIndexStatement(startToken);
|
|
2893
|
+
}
|
|
2894
|
+
throw this.error(this.peek(), "Expected 'TABLE', 'VIEW', 'MATERIALIZED VIEW', or 'INDEX' after ALTER.");
|
|
2895
|
+
}
|
|
2896
|
+
/**
|
|
2897
|
+
* Parse the trailing `{SET|ADD|DROP} TAGS (...)` of an ALTER VIEW /
|
|
2898
|
+
* MATERIALIZED VIEW / INDEX statement (object name already consumed):
|
|
2899
|
+
* SET TAGS → whole-set replace (empty list clears),
|
|
2900
|
+
* ADD TAGS → per-key merge (empty list is a no-op),
|
|
2901
|
+
* DROP TAGS → per-key delete (atomic; empty list is a no-op).
|
|
2902
|
+
* No `(` look-ahead guard is needed (unlike the ALTER TABLE table level):
|
|
2903
|
+
* after `ALTER VIEW <name>` the only legal grammar is a tag op, so the
|
|
2904
|
+
* leading keyword is unambiguous.
|
|
2905
|
+
*/
|
|
2906
|
+
parseObjectTagsAction() {
|
|
2907
|
+
if (this.matchKeyword('SET')) {
|
|
2908
|
+
this.consumeKeyword('TAGS', "Expected 'TAGS' after SET.");
|
|
2909
|
+
return { type: 'setTags', mode: 'replace', tags: this.parseTags() };
|
|
2910
|
+
}
|
|
2911
|
+
if (this.matchKeyword('ADD')) {
|
|
2912
|
+
this.consumeKeyword('TAGS', "Expected 'TAGS' after ADD.");
|
|
2913
|
+
return { type: 'setTags', mode: 'merge', tags: this.parseTags() };
|
|
2914
|
+
}
|
|
2915
|
+
if (this.matchKeyword('DROP')) {
|
|
2916
|
+
this.consumeKeyword('TAGS', "Expected 'TAGS' after DROP.");
|
|
2917
|
+
return { type: 'dropTags', keys: this.parseTagKeys() };
|
|
2918
|
+
}
|
|
2919
|
+
throw this.error(this.peek(), "Expected SET, ADD, or DROP TAGS after object name.");
|
|
2920
|
+
}
|
|
2921
|
+
/** Parse `ALTER VIEW <name> {SET|ADD|DROP} TAGS (...)`. */
|
|
2922
|
+
alterViewStatement(startToken) {
|
|
2923
|
+
this.consumeKeyword('VIEW', "Expected 'VIEW' after ALTER.");
|
|
2924
|
+
const name = this.tableIdentifier();
|
|
2925
|
+
const action = this.parseObjectTagsAction();
|
|
2926
|
+
return { type: 'alterView', name, action, loc: _createLoc(startToken, this.previous()) };
|
|
2927
|
+
}
|
|
2928
|
+
/** Parse `ALTER MATERIALIZED VIEW <name> {SET|ADD|DROP} TAGS (...)`. */
|
|
2929
|
+
alterMaterializedViewStatement(startToken) {
|
|
2930
|
+
this.consumeKeyword('MATERIALIZED', "Expected 'MATERIALIZED' after ALTER.");
|
|
2931
|
+
this.consumeKeyword('VIEW', "Expected 'VIEW' after MATERIALIZED.");
|
|
2932
|
+
const name = this.tableIdentifier();
|
|
2933
|
+
const action = this.parseObjectTagsAction();
|
|
2934
|
+
return { type: 'alterMaterializedView', name, action, loc: _createLoc(startToken, this.previous()) };
|
|
2935
|
+
}
|
|
2936
|
+
/** Parse `ALTER INDEX <name> {SET|ADD|DROP} TAGS (...)`. */
|
|
2937
|
+
alterIndexStatement(startToken) {
|
|
2938
|
+
this.consumeKeyword('INDEX', "Expected 'INDEX' after ALTER.");
|
|
2939
|
+
const name = this.tableIdentifier();
|
|
2940
|
+
const action = this.parseObjectTagsAction();
|
|
2941
|
+
return { type: 'alterIndex', name, action, loc: _createLoc(startToken, this.previous()) };
|
|
2942
|
+
}
|
|
2288
2943
|
/**
|
|
2289
2944
|
* Parse ALTER TABLE statement
|
|
2290
2945
|
* @returns AST for ALTER TABLE statement
|
|
@@ -2296,21 +2951,44 @@ export class Parser {
|
|
|
2296
2951
|
if (this.peekKeyword('RENAME')) {
|
|
2297
2952
|
this.consumeKeyword('RENAME', "Expected RENAME.");
|
|
2298
2953
|
if (this.matchKeyword('COLUMN')) {
|
|
2299
|
-
const oldName = this.consumeIdentifier(
|
|
2954
|
+
const oldName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected old column name after RENAME COLUMN.");
|
|
2300
2955
|
this.consumeKeyword('TO', "Expected 'TO' after old column name.");
|
|
2301
|
-
const newName = this.consumeIdentifier(
|
|
2956
|
+
const newName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected new column name after TO.");
|
|
2302
2957
|
action = { type: 'renameColumn', oldName, newName };
|
|
2303
2958
|
}
|
|
2959
|
+
else if (this.matchKeyword('CONSTRAINT')) {
|
|
2960
|
+
// RENAME CONSTRAINT <old> TO <new> — name-level rename of a named
|
|
2961
|
+
// table-level constraint (CHECK / UNIQUE / FOREIGN KEY).
|
|
2962
|
+
const oldName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected old constraint name after RENAME CONSTRAINT.");
|
|
2963
|
+
this.consumeKeyword('TO', "Expected 'TO' after old constraint name.");
|
|
2964
|
+
const newName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected new constraint name after TO.");
|
|
2965
|
+
action = { type: 'renameConstraint', oldName, newName };
|
|
2966
|
+
}
|
|
2304
2967
|
else {
|
|
2305
2968
|
this.consumeKeyword('TO', "Expected 'TO' after RENAME.");
|
|
2306
|
-
const newName = this.consumeIdentifier(
|
|
2969
|
+
const newName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected new table name after RENAME TO.");
|
|
2307
2970
|
action = { type: 'renameTable', newName };
|
|
2308
2971
|
}
|
|
2309
2972
|
}
|
|
2310
2973
|
else if (this.peekKeyword('ADD')) {
|
|
2311
2974
|
this.consumeKeyword('ADD', "Expected ADD.");
|
|
2312
|
-
if (this.peekKeyword('
|
|
2313
|
-
// ADD
|
|
2975
|
+
if (this.peekKeyword('TAGS') && this.checkNext(1, TokenType.LPAREN)) {
|
|
2976
|
+
// ADD TAGS (...) — per-key merge of table tags. Gated on TAGS being
|
|
2977
|
+
// immediately followed by '(' so a column literally named `tags`
|
|
2978
|
+
// (e.g. `ADD tags integer` / `ADD COLUMN tags ...`) still parses as
|
|
2979
|
+
// ADD COLUMN. `TAGS` is a contextual keyword (a plain identifier), so
|
|
2980
|
+
// without the '(' guard it would shadow such columns.
|
|
2981
|
+
this.consumeKeyword('TAGS', "Expected 'TAGS'.");
|
|
2982
|
+
const tags = this.parseTags();
|
|
2983
|
+
action = { type: 'setTags', target: { kind: 'table' }, mode: 'merge', tags };
|
|
2984
|
+
}
|
|
2985
|
+
else if (this.peekKeyword('CONSTRAINT')
|
|
2986
|
+
|| this.check(TokenType.UNIQUE)
|
|
2987
|
+
|| this.check(TokenType.FOREIGN)
|
|
2988
|
+
|| this.check(TokenType.CHECK)) {
|
|
2989
|
+
// ADD CONSTRAINT <name> <body>, or the unnamed table-constraint forms
|
|
2990
|
+
// ADD UNIQUE (...) / ADD FOREIGN KEY (...) / ADD CHECK (...).
|
|
2991
|
+
// tableConstraint() consumes the optional CONSTRAINT <name> prefix.
|
|
2314
2992
|
const constraint = this.tableConstraint();
|
|
2315
2993
|
action = { type: 'addConstraint', constraint };
|
|
2316
2994
|
}
|
|
@@ -2323,9 +3001,52 @@ export class Parser {
|
|
|
2323
3001
|
}
|
|
2324
3002
|
else if (this.peekKeyword('DROP')) {
|
|
2325
3003
|
this.consumeKeyword('DROP', "Expected DROP.");
|
|
2326
|
-
this.
|
|
2327
|
-
|
|
2328
|
-
|
|
3004
|
+
if (this.peekKeyword('TAGS') && this.checkNext(1, TokenType.LPAREN)) {
|
|
3005
|
+
// DROP TAGS (...) — per-key delete of table tags. Same '(' guard as
|
|
3006
|
+
// ADD TAGS so `DROP COLUMN tags` / `DROP tags` (a column named `tags`)
|
|
3007
|
+
// still parse as DROP COLUMN.
|
|
3008
|
+
this.consumeKeyword('TAGS', "Expected 'TAGS'.");
|
|
3009
|
+
action = { type: 'dropTags', target: { kind: 'table' }, keys: this.parseTagKeys() };
|
|
3010
|
+
}
|
|
3011
|
+
else if (this.peekKeyword('MAINTAINED')) {
|
|
3012
|
+
// DROP MAINTAINED — detach the table's derivation. The verb wins over a
|
|
3013
|
+
// column literally named `maintained`; use DROP COLUMN maintained for that.
|
|
3014
|
+
this.consumeKeyword('MAINTAINED', "Expected 'MAINTAINED'.");
|
|
3015
|
+
action = { type: 'dropMaintained' };
|
|
3016
|
+
}
|
|
3017
|
+
else if (this.matchKeyword('CONSTRAINT')) {
|
|
3018
|
+
// DROP CONSTRAINT <name> — drop a named table-level constraint
|
|
3019
|
+
// (CHECK / UNIQUE / FOREIGN KEY).
|
|
3020
|
+
const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected constraint name after DROP CONSTRAINT.");
|
|
3021
|
+
action = { type: 'dropConstraint', name };
|
|
3022
|
+
}
|
|
3023
|
+
else {
|
|
3024
|
+
this.matchKeyword('COLUMN');
|
|
3025
|
+
const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name after DROP COLUMN.");
|
|
3026
|
+
action = { type: 'dropColumn', name };
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
else if (this.peekKeyword('SET')) {
|
|
3030
|
+
this.consumeKeyword('SET', "Expected SET.");
|
|
3031
|
+
if (this.matchKeyword('MAINTAINED')) {
|
|
3032
|
+
// SET MAINTAINED [(cols)] AS <body> — attach / re-attach a derivation.
|
|
3033
|
+
// No `using` clause: the module is the table's identity. The optional
|
|
3034
|
+
// `(cols)` is the explicit output-column rename list (the differ's
|
|
3035
|
+
// lossless encoding of an MV-sugar `(a, c)` rename): present ⇒ positional
|
|
3036
|
+
// rename + recorded explicit; absent ⇒ implicit (the body's natural
|
|
3037
|
+
// names). The required AS keeps it unambiguous. A trailing
|
|
3038
|
+
// `with defaults (…)` rides inside the body (`select.defaults`).
|
|
3039
|
+
const columns = this.parseMaintainedColumnList();
|
|
3040
|
+
this.consumeKeyword('AS', "Expected 'AS' after SET MAINTAINED.");
|
|
3041
|
+
const select = this.parseQueryExpr(undefined, /*requireReturning*/ true);
|
|
3042
|
+
action = { type: 'setMaintained', columns, select };
|
|
3043
|
+
}
|
|
3044
|
+
else {
|
|
3045
|
+
// Table-level `SET TAGS (...)` — whole-set tag replacement on the table.
|
|
3046
|
+
this.consumeKeyword('TAGS', "Expected 'TAGS' or 'MAINTAINED' after SET.");
|
|
3047
|
+
const tags = this.parseTags();
|
|
3048
|
+
action = { type: 'setTags', target: { kind: 'table' }, mode: 'replace', tags };
|
|
3049
|
+
}
|
|
2329
3050
|
}
|
|
2330
3051
|
else if (this.peekKeyword('ALTER')) {
|
|
2331
3052
|
this.consumeKeyword('ALTER', "Expected ALTER.");
|
|
@@ -2333,14 +3054,37 @@ export class Parser {
|
|
|
2333
3054
|
this.consumeKeyword('COLUMN', "Expected COLUMN.");
|
|
2334
3055
|
action = this.alterColumnAction();
|
|
2335
3056
|
}
|
|
3057
|
+
else if (this.peekKeyword('CONSTRAINT')) {
|
|
3058
|
+
// ALTER CONSTRAINT <name> {SET|ADD|DROP} TAGS (...) — tag mutation on a
|
|
3059
|
+
// named table-level constraint (only named constraints are addressable):
|
|
3060
|
+
// SET TAGS → whole-set replace, ADD TAGS → per-key merge,
|
|
3061
|
+
// DROP TAGS → per-key delete.
|
|
3062
|
+
this.consumeKeyword('CONSTRAINT', "Expected CONSTRAINT.");
|
|
3063
|
+
const constraintName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected constraint name after ALTER CONSTRAINT.");
|
|
3064
|
+
if (this.matchKeyword('SET')) {
|
|
3065
|
+
this.consumeKeyword('TAGS', "Expected 'TAGS' after SET.");
|
|
3066
|
+
action = { type: 'setTags', target: { kind: 'constraint', constraintName }, mode: 'replace', tags: this.parseTags() };
|
|
3067
|
+
}
|
|
3068
|
+
else if (this.matchKeyword('ADD')) {
|
|
3069
|
+
this.consumeKeyword('TAGS', "Expected 'TAGS' after ADD.");
|
|
3070
|
+
action = { type: 'setTags', target: { kind: 'constraint', constraintName }, mode: 'merge', tags: this.parseTags() };
|
|
3071
|
+
}
|
|
3072
|
+
else if (this.matchKeyword('DROP')) {
|
|
3073
|
+
this.consumeKeyword('TAGS', "Expected 'TAGS' after DROP.");
|
|
3074
|
+
action = { type: 'dropTags', target: { kind: 'constraint', constraintName }, keys: this.parseTagKeys() };
|
|
3075
|
+
}
|
|
3076
|
+
else {
|
|
3077
|
+
throw this.error(this.peek(), `Expected SET, ADD, or DROP after ALTER CONSTRAINT ${constraintName}.`);
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
2336
3080
|
else {
|
|
2337
|
-
this.consumeKeyword('PRIMARY', "Expected 'PRIMARY' or '
|
|
3081
|
+
this.consumeKeyword('PRIMARY', "Expected 'PRIMARY', 'COLUMN', or 'CONSTRAINT' after ALTER.");
|
|
2338
3082
|
this.consumeKeyword('KEY', "Expected 'KEY' after PRIMARY.");
|
|
2339
3083
|
this.consume(TokenType.LPAREN, "Expected '(' after PRIMARY KEY.");
|
|
2340
3084
|
const columns = [];
|
|
2341
3085
|
if (!this.check(TokenType.RPAREN)) {
|
|
2342
3086
|
do {
|
|
2343
|
-
const colName = this.consumeIdentifier(
|
|
3087
|
+
const colName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in PRIMARY KEY definition.");
|
|
2344
3088
|
let direction;
|
|
2345
3089
|
if (this.matchKeyword('ASC')) {
|
|
2346
3090
|
direction = 'asc';
|
|
@@ -2371,7 +3115,7 @@ export class Parser {
|
|
|
2371
3115
|
* Caller has already consumed ALTER COLUMN.
|
|
2372
3116
|
*/
|
|
2373
3117
|
alterColumnAction() {
|
|
2374
|
-
const columnName = this.consumeIdentifier(
|
|
3118
|
+
const columnName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name after ALTER COLUMN.");
|
|
2375
3119
|
if (this.matchKeyword('SET')) {
|
|
2376
3120
|
if (this.matchKeyword('NOT')) {
|
|
2377
3121
|
this.consumeKeyword('NULL', "Expected 'NULL' after SET NOT.");
|
|
@@ -2386,7 +3130,29 @@ export class Parser {
|
|
|
2386
3130
|
const expr = this.expression();
|
|
2387
3131
|
return { type: 'alterColumn', columnName, setDefault: expr };
|
|
2388
3132
|
}
|
|
2389
|
-
|
|
3133
|
+
if (this.match(TokenType.COLLATE)) {
|
|
3134
|
+
// ALTER COLUMN <name> SET COLLATE <name> — change the column's collation,
|
|
3135
|
+
// re-sorting / re-validating any PK / UNIQUE / index that orders by it.
|
|
3136
|
+
if (!this.check(TokenType.IDENTIFIER)) {
|
|
3137
|
+
throw this.error(this.peek(), "Expected collation name after SET COLLATE.");
|
|
3138
|
+
}
|
|
3139
|
+
const collation = this.getIdentifierValue(this.advance());
|
|
3140
|
+
return { type: 'alterColumn', columnName, setCollation: collation };
|
|
3141
|
+
}
|
|
3142
|
+
if (this.matchKeyword('TAGS')) {
|
|
3143
|
+
// ALTER COLUMN <name> SET TAGS (...) — whole-set tag replacement on the column.
|
|
3144
|
+
const tags = this.parseTags();
|
|
3145
|
+
return { type: 'setTags', target: { kind: 'column', columnName }, mode: 'replace', tags };
|
|
3146
|
+
}
|
|
3147
|
+
throw this.error(this.peek(), "Expected NOT NULL, DATA TYPE, DEFAULT, COLLATE, or TAGS after SET.");
|
|
3148
|
+
}
|
|
3149
|
+
if (this.matchKeyword('ADD')) {
|
|
3150
|
+
// ALTER COLUMN <name> ADD TAGS (...) — per-key merge on the column. TAGS is
|
|
3151
|
+
// unambiguous here (the grammar after ALTER COLUMN <name> ADD is fixed), so
|
|
3152
|
+
// no '(' look-ahead guard is needed as it is at the table level.
|
|
3153
|
+
this.consumeKeyword('TAGS', "Expected 'TAGS' after ADD.");
|
|
3154
|
+
const tags = this.parseTags();
|
|
3155
|
+
return { type: 'setTags', target: { kind: 'column', columnName }, mode: 'merge', tags };
|
|
2390
3156
|
}
|
|
2391
3157
|
if (this.matchKeyword('DROP')) {
|
|
2392
3158
|
if (this.matchKeyword('NOT')) {
|
|
@@ -2396,9 +3162,13 @@ export class Parser {
|
|
|
2396
3162
|
if (this.matchKeyword('DEFAULT')) {
|
|
2397
3163
|
return { type: 'alterColumn', columnName, setDefault: null };
|
|
2398
3164
|
}
|
|
2399
|
-
|
|
3165
|
+
if (this.matchKeyword('TAGS')) {
|
|
3166
|
+
// ALTER COLUMN <name> DROP TAGS (...) — per-key delete on the column.
|
|
3167
|
+
return { type: 'dropTags', target: { kind: 'column', columnName }, keys: this.parseTagKeys() };
|
|
3168
|
+
}
|
|
3169
|
+
throw this.error(this.peek(), "Expected NOT NULL, DEFAULT, or TAGS after DROP.");
|
|
2400
3170
|
}
|
|
2401
|
-
throw this.error(this.peek(), "Expected SET or DROP after ALTER COLUMN name.");
|
|
3171
|
+
throw this.error(this.peek(), "Expected SET, ADD, or DROP after ALTER COLUMN name.");
|
|
2402
3172
|
}
|
|
2403
3173
|
/**
|
|
2404
3174
|
* Parse a data-type name as used in column definitions. Supports optional
|
|
@@ -2486,16 +3256,20 @@ export class Parser {
|
|
|
2486
3256
|
return { type: 'pragma', ...nameValue, loc: _createLoc(startToken, this.previous()) };
|
|
2487
3257
|
}
|
|
2488
3258
|
/**
|
|
2489
|
-
* Parse ANALYZE statement: ANALYZE [schema.]table | ANALYZE
|
|
3259
|
+
* Parse ANALYZE statement: ANALYZE [schema.]table | ANALYZE schema.* | ANALYZE
|
|
2490
3260
|
*/
|
|
2491
3261
|
analyzeStatement(startToken) {
|
|
2492
3262
|
// ANALYZE with no arguments → analyze all tables
|
|
2493
3263
|
if (this.isAtEnd() || this.check(TokenType.SEMICOLON)) {
|
|
2494
3264
|
return { type: 'analyze', loc: _createLoc(startToken, this.previous()) };
|
|
2495
3265
|
}
|
|
2496
|
-
// Parse optional schema.table or just table
|
|
3266
|
+
// Parse optional schema.table, schema.* (all tables in schema), or just table
|
|
2497
3267
|
const name1 = this.consumeIdentifier([], "Expected table name after ANALYZE.");
|
|
2498
3268
|
if (this.match(TokenType.DOT)) {
|
|
3269
|
+
// ANALYZE schema.* → analyze every table in the schema (schema-only shape)
|
|
3270
|
+
if (this.match(TokenType.ASTERISK)) {
|
|
3271
|
+
return { type: 'analyze', schemaName: name1, loc: _createLoc(startToken, this.previous()) };
|
|
3272
|
+
}
|
|
2499
3273
|
const name2 = this.consumeIdentifier([], "Expected table name after schema qualifier.");
|
|
2500
3274
|
return { type: 'analyze', schemaName: name1, tableName: name2, loc: _createLoc(startToken, this.previous()) };
|
|
2501
3275
|
}
|
|
@@ -2503,6 +3277,8 @@ export class Parser {
|
|
|
2503
3277
|
}
|
|
2504
3278
|
// === Declarative schema parsing ===
|
|
2505
3279
|
declareSchemaStatement(startToken) {
|
|
3280
|
+
// Optional contextual keyword: `declare logical schema X { ... }`.
|
|
3281
|
+
const isLogical = this.matchKeyword('LOGICAL');
|
|
2506
3282
|
this.consumeKeyword('SCHEMA', "Expected 'SCHEMA' after DECLARE.");
|
|
2507
3283
|
const schemaName = this.consumeIdentifier(['temp', 'temporary'], "Expected schema name after DECLARE.");
|
|
2508
3284
|
let version;
|
|
@@ -2560,6 +3336,11 @@ export class Parser {
|
|
|
2560
3336
|
this.consumeKeyword('INDEX', "Expected 'INDEX' after 'UNIQUE'.");
|
|
2561
3337
|
items.push(this.declareIndexItem(true));
|
|
2562
3338
|
}
|
|
3339
|
+
else if (this.peekKeyword('MATERIALIZED')) {
|
|
3340
|
+
this.advance();
|
|
3341
|
+
this.consumeKeyword('VIEW', "Expected 'VIEW' after 'MATERIALIZED'.");
|
|
3342
|
+
items.push(this.declareMaterializedViewItem());
|
|
3343
|
+
}
|
|
2563
3344
|
else if (this.peekKeyword('VIEW')) {
|
|
2564
3345
|
this.advance();
|
|
2565
3346
|
items.push(this.declareViewItem());
|
|
@@ -2586,10 +3367,52 @@ export class Parser {
|
|
|
2586
3367
|
}
|
|
2587
3368
|
this.consume(TokenType.RBRACE, "Expected '}' to close schema declaration block.");
|
|
2588
3369
|
const endTok = this.previous();
|
|
2589
|
-
return { type: 'declareSchema', schemaName, version, using, items, loc: _createLoc(startToken, endTok) };
|
|
3370
|
+
return { type: 'declareSchema', schemaName, version, using, items, ...(isLogical ? { isLogical: true } : {}), loc: _createLoc(startToken, endTok) };
|
|
3371
|
+
}
|
|
3372
|
+
/**
|
|
3373
|
+
* Parses `declare lens for <X> over <Y> { ( view <T> as <select> ;? )* }`.
|
|
3374
|
+
* The DECLARE token is already consumed by {@link statement}. `lens` and `for`
|
|
3375
|
+
* are contextual keywords (matched via peekKeyword's IDENTIFIER fallback);
|
|
3376
|
+
* `over` is the existing window-function keyword.
|
|
3377
|
+
*/
|
|
3378
|
+
declareLensStatement(startToken) {
|
|
3379
|
+
this.consumeKeyword('LENS', "Expected 'LENS' after DECLARE.");
|
|
3380
|
+
this.consumeKeyword('FOR', "Expected 'FOR' after DECLARE LENS.");
|
|
3381
|
+
const logicalSchema = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected logical schema name after 'FOR'.");
|
|
3382
|
+
this.consumeKeyword('OVER', "Expected 'OVER' after the logical schema name.");
|
|
3383
|
+
const basisSchema = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected basis schema name after 'OVER'.");
|
|
3384
|
+
this.consume(TokenType.LBRACE, "Expected '{' to start the lens declaration block.");
|
|
3385
|
+
const overrides = [];
|
|
3386
|
+
while (!this.check(TokenType.RBRACE)) {
|
|
3387
|
+
if (this.isAtEnd())
|
|
3388
|
+
break;
|
|
3389
|
+
this.consumeKeyword('VIEW', "Expected 'view' to begin a lens override.");
|
|
3390
|
+
const table = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected the logical table name after 'view'.");
|
|
3391
|
+
this.consumeKeyword('AS', "Expected 'AS' after the logical table name.");
|
|
3392
|
+
const body = this.parseQueryExpr();
|
|
3393
|
+
if (body.type !== 'select') {
|
|
3394
|
+
throw this.error(this.previous(), `A lens override body must be a SELECT; got '${body.type}'.`);
|
|
3395
|
+
}
|
|
3396
|
+
// A compound set-operation parses as a single `select` node carrying a
|
|
3397
|
+
// `compound` (or legacy `union`) pointer; the override merger composes
|
|
3398
|
+
// only the top leg, so reject the shape rather than silently mis-map.
|
|
3399
|
+
if (body.compound || body.union) {
|
|
3400
|
+
throw this.error(this.previous(), `A lens override body must be a single SELECT; compound set-operations (union/intersect/except) are not supported in v1 lens overrides.`);
|
|
3401
|
+
}
|
|
3402
|
+
overrides.push({ table, select: body });
|
|
3403
|
+
this.match(TokenType.SEMICOLON);
|
|
3404
|
+
}
|
|
3405
|
+
this.consume(TokenType.RBRACE, "Expected '}' to close the lens declaration block.");
|
|
3406
|
+
return {
|
|
3407
|
+
type: 'declareLens',
|
|
3408
|
+
logicalSchema,
|
|
3409
|
+
basisSchema,
|
|
3410
|
+
overrides,
|
|
3411
|
+
loc: _createLoc(startToken, this.previous()),
|
|
3412
|
+
};
|
|
2590
3413
|
}
|
|
2591
3414
|
declareTableItem() {
|
|
2592
|
-
const tableName = this.consumeIdentifier(
|
|
3415
|
+
const tableName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, 'Expected table name in declaration.');
|
|
2593
3416
|
let moduleName;
|
|
2594
3417
|
let moduleArgs;
|
|
2595
3418
|
const columns = [];
|
|
@@ -2648,6 +3471,10 @@ export class Parser {
|
|
|
2648
3471
|
else {
|
|
2649
3472
|
this.consume(TokenType.RPAREN, "Expected ')' after table definition.");
|
|
2650
3473
|
}
|
|
3474
|
+
// Optional `maintained as <body … with defaults (…)>` — the declared-shape
|
|
3475
|
+
// maintained-table form, carried through the CreateTableStmt reuse. (Differ
|
|
3476
|
+
// transition handling is a follow-up; the clause parses and round-trips.)
|
|
3477
|
+
const maintained = this.parseMaintainedClause();
|
|
2651
3478
|
// Parse trailing WITH clauses (CONTEXT, TAGS) in any order
|
|
2652
3479
|
let contextDefinitions;
|
|
2653
3480
|
let tags;
|
|
@@ -2676,11 +3503,11 @@ export class Parser {
|
|
|
2676
3503
|
ifNotExists: false,
|
|
2677
3504
|
columns,
|
|
2678
3505
|
constraints,
|
|
2679
|
-
isTemporary: false,
|
|
2680
3506
|
moduleName,
|
|
2681
3507
|
moduleArgs,
|
|
2682
3508
|
contextDefinitions,
|
|
2683
|
-
tags
|
|
3509
|
+
tags,
|
|
3510
|
+
maintained
|
|
2684
3511
|
};
|
|
2685
3512
|
return { type: 'declaredTable', tableStmt };
|
|
2686
3513
|
}
|
|
@@ -2691,6 +3518,11 @@ export class Parser {
|
|
|
2691
3518
|
this.consume(TokenType.LPAREN, "Expected '(' before index columns.");
|
|
2692
3519
|
const columns = this.indexedColumnList();
|
|
2693
3520
|
this.consume(TokenType.RPAREN, "Expected ')' after index columns.");
|
|
3521
|
+
// Parse optional WHERE <predicate> (partial index), before WITH TAGS
|
|
3522
|
+
let where;
|
|
3523
|
+
if (this.matchKeyword('WHERE')) {
|
|
3524
|
+
where = this.expression();
|
|
3525
|
+
}
|
|
2694
3526
|
// Parse optional WITH TAGS
|
|
2695
3527
|
let tags;
|
|
2696
3528
|
if (this.matchKeyword('WITH')) {
|
|
@@ -2707,6 +3539,7 @@ export class Parser {
|
|
|
2707
3539
|
table: { type: 'identifier', name: tableName },
|
|
2708
3540
|
ifNotExists: false,
|
|
2709
3541
|
columns,
|
|
3542
|
+
where,
|
|
2710
3543
|
isUnique,
|
|
2711
3544
|
tags
|
|
2712
3545
|
};
|
|
@@ -2719,9 +3552,10 @@ export class Parser {
|
|
|
2719
3552
|
columns = this.identifierList();
|
|
2720
3553
|
this.consume(TokenType.RPAREN, "Expected ')' after view columns.");
|
|
2721
3554
|
}
|
|
2722
|
-
this.consumeKeyword('AS', "Expected AS before
|
|
2723
|
-
const
|
|
2724
|
-
|
|
3555
|
+
this.consumeKeyword('AS', "Expected AS before view body in view declaration.");
|
|
3556
|
+
const select = this.parseQueryExpr(undefined, /*requireReturning*/ true);
|
|
3557
|
+
// A trailing `with defaults (…)` rides inside the select body
|
|
3558
|
+
// (`select.defaults`); only the DDL-level WITH TAGS is parsed here.
|
|
2725
3559
|
// Parse optional WITH TAGS
|
|
2726
3560
|
let tags;
|
|
2727
3561
|
if (this.matchKeyword('WITH')) {
|
|
@@ -2738,11 +3572,71 @@ export class Parser {
|
|
|
2738
3572
|
ifNotExists: false,
|
|
2739
3573
|
columns,
|
|
2740
3574
|
select,
|
|
2741
|
-
isTemporary: false,
|
|
2742
3575
|
tags
|
|
2743
3576
|
};
|
|
2744
3577
|
return { type: 'declaredView', viewStmt };
|
|
2745
3578
|
}
|
|
3579
|
+
declareMaterializedViewItem() {
|
|
3580
|
+
const viewName = this.consumeIdentifier('Expected materialized view name.');
|
|
3581
|
+
let columns;
|
|
3582
|
+
if (this.match(TokenType.LPAREN)) {
|
|
3583
|
+
columns = this.identifierList();
|
|
3584
|
+
this.consume(TokenType.RPAREN, "Expected ')' after materialized view columns.");
|
|
3585
|
+
}
|
|
3586
|
+
// Optional backing-module clause (`using mem(...)`) before the body — same
|
|
3587
|
+
// shape as the top-level CREATE MATERIALIZED VIEW form.
|
|
3588
|
+
let moduleName;
|
|
3589
|
+
const moduleArgs = {};
|
|
3590
|
+
if (this.matchKeyword('USING')) {
|
|
3591
|
+
moduleName = this.consumeIdentifier("Expected module name after 'USING'.");
|
|
3592
|
+
if (this.match(TokenType.LPAREN)) {
|
|
3593
|
+
let positionalIndex = 0;
|
|
3594
|
+
if (!this.check(TokenType.RPAREN)) {
|
|
3595
|
+
do {
|
|
3596
|
+
if (this.check(TokenType.STRING) || this.check(TokenType.INTEGER) || this.check(TokenType.FLOAT)) {
|
|
3597
|
+
const token = this.advance();
|
|
3598
|
+
moduleArgs[String(positionalIndex++)] = token.literal;
|
|
3599
|
+
}
|
|
3600
|
+
else if (this.check(TokenType.IDENTIFIER)) {
|
|
3601
|
+
const nameValue = this.nameValueItem('module argument');
|
|
3602
|
+
moduleArgs[nameValue.name] = nameValue.value && nameValue.value.type === 'literal'
|
|
3603
|
+
? getSyncLiteral(nameValue.value)
|
|
3604
|
+
: (nameValue.value && nameValue.value.type === 'identifier' ? nameValue.value.name : nameValue.name);
|
|
3605
|
+
}
|
|
3606
|
+
else {
|
|
3607
|
+
throw this.error(this.peek(), "Expected module argument (string, number, or name=value pair).");
|
|
3608
|
+
}
|
|
3609
|
+
} while (this.match(TokenType.COMMA));
|
|
3610
|
+
}
|
|
3611
|
+
this.consume(TokenType.RPAREN, "Expected ')' after module arguments.");
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
this.consumeKeyword('AS', "Expected AS before view body in materialized view declaration.");
|
|
3615
|
+
const select = this.parseQueryExpr(undefined, /*requireReturning*/ true);
|
|
3616
|
+
// A trailing `with defaults (…)` rides inside the select body
|
|
3617
|
+
// (`select.defaults`); only the DDL-level WITH TAGS is parsed here.
|
|
3618
|
+
// Parse optional WITH TAGS
|
|
3619
|
+
let tags;
|
|
3620
|
+
if (this.matchKeyword('WITH')) {
|
|
3621
|
+
if (this.matchKeyword('TAGS')) {
|
|
3622
|
+
tags = this.parseTags();
|
|
3623
|
+
}
|
|
3624
|
+
else {
|
|
3625
|
+
this.current--;
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
const viewStmt = {
|
|
3629
|
+
type: 'createMaterializedView',
|
|
3630
|
+
view: { type: 'identifier', name: viewName },
|
|
3631
|
+
ifNotExists: false,
|
|
3632
|
+
columns,
|
|
3633
|
+
select,
|
|
3634
|
+
moduleName,
|
|
3635
|
+
moduleArgs: moduleName && Object.keys(moduleArgs).length > 0 ? moduleArgs : undefined,
|
|
3636
|
+
tags
|
|
3637
|
+
};
|
|
3638
|
+
return { type: 'declaredMaterializedView', viewStmt };
|
|
3639
|
+
}
|
|
2746
3640
|
declareSeedItem() {
|
|
2747
3641
|
// seed <table> ( (...), (...) ) or seed <table> values (col, ...) values (...), (...)
|
|
2748
3642
|
const tableName = this.consumeIdentifier('Expected table name after SEED.');
|
|
@@ -3027,7 +3921,7 @@ export class Parser {
|
|
|
3027
3921
|
// --- Stubs for required helpers (implement fully for CREATE TABLE) ---
|
|
3028
3922
|
/** @internal Parses a column definition */
|
|
3029
3923
|
columnDefinition() {
|
|
3030
|
-
const name = this.consumeIdentifier(
|
|
3924
|
+
const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name.");
|
|
3031
3925
|
let dataType;
|
|
3032
3926
|
if (this.check(TokenType.IDENTIFIER)) {
|
|
3033
3927
|
dataType = this.advance().lexeme;
|
|
@@ -3184,6 +4078,22 @@ export class Parser {
|
|
|
3184
4078
|
this.consume(TokenType.RPAREN, "Expected ')' after tag list.");
|
|
3185
4079
|
return tags;
|
|
3186
4080
|
}
|
|
4081
|
+
/**
|
|
4082
|
+
* @internal Parses a bare comma-list of tag keys — `(key [, key ...])` with no
|
|
4083
|
+
* `= value`, used by the DROP TAGS form. Mirrors {@link parseTags} but yields
|
|
4084
|
+
* just the keys. An empty list `()` yields `[]`.
|
|
4085
|
+
*/
|
|
4086
|
+
parseTagKeys() {
|
|
4087
|
+
this.consume(TokenType.LPAREN, "Expected '(' after TAGS.");
|
|
4088
|
+
const keys = [];
|
|
4089
|
+
if (!this.check(TokenType.RPAREN)) {
|
|
4090
|
+
do {
|
|
4091
|
+
keys.push(this.consumeIdentifier("Expected tag key identifier."));
|
|
4092
|
+
} while (this.match(TokenType.COMMA));
|
|
4093
|
+
}
|
|
4094
|
+
this.consume(TokenType.RPAREN, "Expected ')' after tag key list.");
|
|
4095
|
+
return keys;
|
|
4096
|
+
}
|
|
3187
4097
|
/** @internal Parses a tag value: string, number, TRUE, FALSE, or NULL */
|
|
3188
4098
|
parseTagValue() {
|
|
3189
4099
|
if (this.match(TokenType.STRING)) {
|
|
@@ -3418,7 +4328,19 @@ export class Parser {
|
|
|
3418
4328
|
if (this.check(TokenType.REFERENCES)) {
|
|
3419
4329
|
this.advance();
|
|
3420
4330
|
}
|
|
3421
|
-
|
|
4331
|
+
// Optional `schema.` qualifier on the parent table (cross-schema FK),
|
|
4332
|
+
// mirroring tableIdentifier() so a schema named `temp` parses.
|
|
4333
|
+
const contextualKeywords = [...CONTEXTUAL_KEYWORDS, 'temp', 'temporary'];
|
|
4334
|
+
let schema;
|
|
4335
|
+
let table;
|
|
4336
|
+
if (this.checkIdentifierLike(contextualKeywords) && this.checkNext(1, TokenType.DOT)) {
|
|
4337
|
+
schema = this.consumeIdentifier(contextualKeywords, "Expected schema name.");
|
|
4338
|
+
this.advance(); // Consume DOT
|
|
4339
|
+
table = this.consumeIdentifier(contextualKeywords, "Expected foreign table name after schema.");
|
|
4340
|
+
}
|
|
4341
|
+
else {
|
|
4342
|
+
table = this.consumeIdentifier(contextualKeywords, "Expected foreign table name.");
|
|
4343
|
+
}
|
|
3422
4344
|
let columns;
|
|
3423
4345
|
if (this.match(TokenType.LPAREN)) {
|
|
3424
4346
|
columns = this.identifierList();
|
|
@@ -3473,7 +4395,7 @@ export class Parser {
|
|
|
3473
4395
|
break;
|
|
3474
4396
|
}
|
|
3475
4397
|
}
|
|
3476
|
-
return { table, columns, onDelete, onUpdate, deferrable, initiallyDeferred };
|
|
4398
|
+
return { table, schema, columns, onDelete, onUpdate, deferrable, initiallyDeferred };
|
|
3477
4399
|
}
|
|
3478
4400
|
/** @internal Parses the ON CONFLICT clause */
|
|
3479
4401
|
parseConflictClause() {
|
|
@@ -3518,7 +4440,7 @@ export class Parser {
|
|
|
3518
4440
|
identifierList() {
|
|
3519
4441
|
const identifiers = [];
|
|
3520
4442
|
do {
|
|
3521
|
-
identifiers.push(this.consumeIdentifier(
|
|
4443
|
+
identifiers.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected identifier in list."));
|
|
3522
4444
|
} while (this.match(TokenType.COMMA));
|
|
3523
4445
|
return identifiers;
|
|
3524
4446
|
}
|
|
@@ -3526,7 +4448,7 @@ export class Parser {
|
|
|
3526
4448
|
identifierListWithDirection() {
|
|
3527
4449
|
const identifiers = [];
|
|
3528
4450
|
do {
|
|
3529
|
-
const name = this.consumeIdentifier(
|
|
4451
|
+
const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected identifier in list.");
|
|
3530
4452
|
const direction = this.match(TokenType.ASC) ? 'asc' : this.match(TokenType.DESC) ? 'desc' : undefined;
|
|
3531
4453
|
identifiers.push({ name, direction });
|
|
3532
4454
|
} while (this.match(TokenType.COMMA));
|