@quereus/quereus 4.8.0 → 4.10.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/dist/src/core/database.d.ts +6 -0
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +45 -5
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/core/statement.d.ts.map +1 -1
- package/dist/src/core/statement.js +74 -15
- package/dist/src/core/statement.js.map +1 -1
- package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
- package/dist/src/func/builtins/aggregate.js +88 -35
- package/dist/src/func/builtins/aggregate.js.map +1 -1
- package/dist/src/func/builtins/conversion.d.ts +5 -0
- package/dist/src/func/builtins/conversion.d.ts.map +1 -1
- package/dist/src/func/builtins/conversion.js +16 -2
- package/dist/src/func/builtins/conversion.js.map +1 -1
- package/dist/src/func/builtins/explain.d.ts.map +1 -1
- package/dist/src/func/builtins/explain.js +9 -2
- 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 +5 -2
- package/dist/src/func/builtins/index.js.map +1 -1
- package/dist/src/func/builtins/scalar.d.ts.map +1 -1
- package/dist/src/func/builtins/scalar.js +4 -2
- package/dist/src/func/builtins/scalar.js.map +1 -1
- package/dist/src/func/builtins/string.d.ts +2 -0
- package/dist/src/func/builtins/string.d.ts.map +1 -1
- package/dist/src/func/builtins/string.js +60 -4
- package/dist/src/func/builtins/string.js.map +1 -1
- package/dist/src/func/registration.d.ts +6 -0
- package/dist/src/func/registration.d.ts.map +1 -1
- package/dist/src/func/registration.js +1 -0
- package/dist/src/func/registration.js.map +1 -1
- package/dist/src/index.d.ts +7 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +21 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/planner/building/constraint-builder.js +1 -1
- package/dist/src/planner/building/constraint-builder.js.map +1 -1
- package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
- package/dist/src/planner/nodes/scalar.js +13 -1
- package/dist/src/planner/nodes/scalar.js.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +121 -51
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
- package/dist/src/planner/rules/shared/index-style-context.d.ts +14 -4
- package/dist/src/planner/rules/shared/index-style-context.d.ts.map +1 -1
- package/dist/src/planner/rules/shared/index-style-context.js +14 -4
- package/dist/src/planner/rules/shared/index-style-context.js.map +1 -1
- package/dist/src/runtime/emission-context.d.ts +14 -1
- package/dist/src/runtime/emission-context.d.ts.map +1 -1
- package/dist/src/runtime/emission-context.js +17 -5
- package/dist/src/runtime/emission-context.js.map +1 -1
- package/dist/src/runtime/emit/aggregate-setup.d.ts +44 -8
- package/dist/src/runtime/emit/aggregate-setup.d.ts.map +1 -1
- package/dist/src/runtime/emit/aggregate-setup.js +79 -15
- package/dist/src/runtime/emit/aggregate-setup.js.map +1 -1
- package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/aggregate.js +21 -106
- package/dist/src/runtime/emit/aggregate.js.map +1 -1
- package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/alter-table.js +212 -155
- package/dist/src/runtime/emit/alter-table.js.map +1 -1
- package/dist/src/runtime/emit/asof-scan.js +1 -1
- package/dist/src/runtime/emit/asof-scan.js.map +1 -1
- package/dist/src/runtime/emit/assertion-drop-guard.d.ts +26 -12
- package/dist/src/runtime/emit/assertion-drop-guard.d.ts.map +1 -1
- package/dist/src/runtime/emit/assertion-drop-guard.js +45 -26
- package/dist/src/runtime/emit/assertion-drop-guard.js.map +1 -1
- package/dist/src/runtime/emit/assertion-rename-helpers.d.ts +6 -6
- package/dist/src/runtime/emit/assertion-rename-helpers.d.ts.map +1 -1
- package/dist/src/runtime/emit/assertion-rename-helpers.js +18 -20
- package/dist/src/runtime/emit/assertion-rename-helpers.js.map +1 -1
- package/dist/src/runtime/emit/between.d.ts +2 -0
- package/dist/src/runtime/emit/between.d.ts.map +1 -1
- package/dist/src/runtime/emit/between.js +7 -8
- package/dist/src/runtime/emit/between.js.map +1 -1
- package/dist/src/runtime/emit/binary.d.ts +27 -5
- package/dist/src/runtime/emit/binary.d.ts.map +1 -1
- package/dist/src/runtime/emit/binary.js +278 -187
- package/dist/src/runtime/emit/binary.js.map +1 -1
- package/dist/src/runtime/emit/case.d.ts +34 -0
- package/dist/src/runtime/emit/case.d.ts.map +1 -1
- package/dist/src/runtime/emit/case.js +18 -11
- package/dist/src/runtime/emit/case.js.map +1 -1
- package/dist/src/runtime/emit/cast.d.ts +2 -0
- package/dist/src/runtime/emit/cast.d.ts.map +1 -1
- package/dist/src/runtime/emit/cast.js +7 -5
- package/dist/src/runtime/emit/cast.js.map +1 -1
- package/dist/src/runtime/emit/column-reference.d.ts +3 -1
- package/dist/src/runtime/emit/column-reference.d.ts.map +1 -1
- package/dist/src/runtime/emit/column-reference.js +6 -2
- package/dist/src/runtime/emit/column-reference.js.map +1 -1
- package/dist/src/runtime/emit/column-rename-cascade.d.ts +84 -0
- package/dist/src/runtime/emit/column-rename-cascade.d.ts.map +1 -0
- package/dist/src/runtime/emit/column-rename-cascade.js +134 -0
- package/dist/src/runtime/emit/column-rename-cascade.js.map +1 -0
- package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
- package/dist/src/runtime/emit/dml-executor.js +21 -0
- package/dist/src/runtime/emit/dml-executor.js.map +1 -1
- package/dist/src/runtime/emit/drop-column-guards.d.ts +82 -53
- package/dist/src/runtime/emit/drop-column-guards.d.ts.map +1 -1
- package/dist/src/runtime/emit/drop-column-guards.js +155 -90
- package/dist/src/runtime/emit/drop-column-guards.js.map +1 -1
- package/dist/src/runtime/emit/drop-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/drop-table.js +5 -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 +5 -0
- package/dist/src/runtime/emit/drop-view.js.map +1 -1
- package/dist/src/runtime/emit/expression-drop-guard.d.ts +57 -0
- package/dist/src/runtime/emit/expression-drop-guard.d.ts.map +1 -0
- package/dist/src/runtime/emit/expression-drop-guard.js +82 -0
- package/dist/src/runtime/emit/expression-drop-guard.js.map +1 -0
- package/dist/src/runtime/emit/filter.d.ts +1 -1
- package/dist/src/runtime/emit/filter.js +1 -1
- package/dist/src/runtime/emit/hash-aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/hash-aggregate.js +18 -88
- package/dist/src/runtime/emit/hash-aggregate.js.map +1 -1
- package/dist/src/runtime/emit/literal.d.ts +14 -1
- package/dist/src/runtime/emit/literal.d.ts.map +1 -1
- package/dist/src/runtime/emit/literal.js +33 -7
- package/dist/src/runtime/emit/literal.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts +33 -10
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.js +158 -49
- package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view.js +3 -0
- package/dist/src/runtime/emit/materialized-view.js.map +1 -1
- package/dist/src/runtime/emit/parameter.d.ts +3 -1
- package/dist/src/runtime/emit/parameter.d.ts.map +1 -1
- package/dist/src/runtime/emit/parameter.js +7 -2
- package/dist/src/runtime/emit/parameter.js.map +1 -1
- package/dist/src/runtime/emit/scalar-function.d.ts +29 -1
- package/dist/src/runtime/emit/scalar-function.d.ts.map +1 -1
- package/dist/src/runtime/emit/scalar-function.js +66 -16
- package/dist/src/runtime/emit/scalar-function.js.map +1 -1
- package/dist/src/runtime/emit/scalar-op.d.ts +63 -0
- package/dist/src/runtime/emit/scalar-op.d.ts.map +1 -0
- package/dist/src/runtime/emit/scalar-op.js +37 -0
- package/dist/src/runtime/emit/scalar-op.js.map +1 -0
- package/dist/src/runtime/emit/scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/scan.js +19 -1
- package/dist/src/runtime/emit/scan.js.map +1 -1
- package/dist/src/runtime/emit/temporal-arithmetic.d.ts +18 -8
- package/dist/src/runtime/emit/temporal-arithmetic.d.ts.map +1 -1
- package/dist/src/runtime/emit/temporal-arithmetic.js +29 -311
- package/dist/src/runtime/emit/temporal-arithmetic.js.map +1 -1
- package/dist/src/runtime/emit/unary.d.ts +2 -0
- package/dist/src/runtime/emit/unary.d.ts.map +1 -1
- package/dist/src/runtime/emit/unary.js +125 -53
- package/dist/src/runtime/emit/unary.js.map +1 -1
- package/dist/src/runtime/emitters.d.ts +22 -0
- package/dist/src/runtime/emitters.d.ts.map +1 -1
- package/dist/src/runtime/emitters.js +29 -0
- package/dist/src/runtime/emitters.js.map +1 -1
- package/dist/src/runtime/scalar-fusion.d.ts +47 -0
- package/dist/src/runtime/scalar-fusion.d.ts.map +1 -0
- package/dist/src/runtime/scalar-fusion.js +275 -0
- package/dist/src/runtime/scalar-fusion.js.map +1 -0
- package/dist/src/runtime/strict-flags.d.ts +9 -0
- package/dist/src/runtime/strict-flags.d.ts.map +1 -1
- package/dist/src/runtime/strict-flags.js +9 -0
- package/dist/src/runtime/strict-flags.js.map +1 -1
- package/dist/src/runtime/strict-fork.js +2 -2
- package/dist/src/runtime/strict-fork.js.map +1 -1
- package/dist/src/runtime/strict-representation.d.ts +78 -0
- package/dist/src/runtime/strict-representation.d.ts.map +1 -0
- package/dist/src/runtime/strict-representation.js +159 -0
- package/dist/src/runtime/strict-representation.js.map +1 -0
- package/dist/src/schema/assertion.d.ts +58 -0
- package/dist/src/schema/assertion.d.ts.map +1 -1
- package/dist/src/schema/assertion.js +78 -0
- package/dist/src/schema/assertion.js.map +1 -1
- package/dist/src/schema/catalog-persistability.d.ts +11 -3
- package/dist/src/schema/catalog-persistability.d.ts.map +1 -1
- package/dist/src/schema/catalog-persistability.js +48 -29
- package/dist/src/schema/catalog-persistability.js.map +1 -1
- package/dist/src/schema/catalog.d.ts +22 -0
- package/dist/src/schema/catalog.d.ts.map +1 -1
- package/dist/src/schema/catalog.js +4 -1
- package/dist/src/schema/catalog.js.map +1 -1
- package/dist/src/schema/column-republication.d.ts +126 -0
- package/dist/src/schema/column-republication.d.ts.map +1 -0
- package/dist/src/schema/column-republication.js +121 -0
- package/dist/src/schema/column-republication.js.map +1 -0
- package/dist/src/schema/column-source-resolver.d.ts +29 -6
- package/dist/src/schema/column-source-resolver.d.ts.map +1 -1
- package/dist/src/schema/column-source-resolver.js +78 -9
- package/dist/src/schema/column-source-resolver.js.map +1 -1
- package/dist/src/schema/expression-dependents.d.ts +160 -0
- package/dist/src/schema/expression-dependents.d.ts.map +1 -0
- package/dist/src/schema/expression-dependents.js +228 -0
- package/dist/src/schema/expression-dependents.js.map +1 -0
- package/dist/src/schema/function.d.ts +15 -0
- package/dist/src/schema/function.d.ts.map +1 -1
- package/dist/src/schema/function.js.map +1 -1
- package/dist/src/schema/object-dependency-closure.d.ts +127 -0
- package/dist/src/schema/object-dependency-closure.d.ts.map +1 -0
- package/dist/src/schema/object-dependency-closure.js +117 -0
- package/dist/src/schema/object-dependency-closure.js.map +1 -0
- package/dist/src/schema/object-ref-resolver.d.ts +83 -0
- package/dist/src/schema/object-ref-resolver.d.ts.map +1 -0
- package/dist/src/schema/object-ref-resolver.js +135 -0
- package/dist/src/schema/object-ref-resolver.js.map +1 -0
- package/dist/src/schema/rename/column-rename.d.ts +228 -0
- package/dist/src/schema/rename/column-rename.d.ts.map +1 -0
- package/dist/src/schema/rename/column-rename.js +1225 -0
- package/dist/src/schema/rename/column-rename.js.map +1 -0
- package/dist/src/schema/rename/self-qualifier-strip.d.ts +29 -0
- package/dist/src/schema/rename/self-qualifier-strip.d.ts.map +1 -0
- package/dist/src/schema/rename/self-qualifier-strip.js +267 -0
- package/dist/src/schema/rename/self-qualifier-strip.js.map +1 -0
- package/dist/src/schema/rename/shared.d.ts +108 -0
- package/dist/src/schema/rename/shared.d.ts.map +1 -0
- package/dist/src/schema/rename/shared.js +49 -0
- package/dist/src/schema/rename/shared.js.map +1 -0
- package/dist/src/schema/rename/table-rename.d.ts +263 -0
- package/dist/src/schema/rename/table-rename.d.ts.map +1 -0
- package/dist/src/schema/rename/table-rename.js +860 -0
- package/dist/src/schema/rename/table-rename.js.map +1 -0
- package/dist/src/schema/rename-rewriter.d.ts +18 -278
- package/dist/src/schema/rename-rewriter.d.ts.map +1 -1
- package/dist/src/schema/rename-rewriter.js +17 -1506
- package/dist/src/schema/rename-rewriter.js.map +1 -1
- package/dist/src/schema/schema-differ.d.ts.map +1 -1
- package/dist/src/schema/schema-differ.js +228 -21
- package/dist/src/schema/schema-differ.js.map +1 -1
- package/dist/src/types/builtin-types.d.ts.map +1 -1
- package/dist/src/types/builtin-types.js +29 -36
- package/dist/src/types/builtin-types.js.map +1 -1
- package/dist/src/types/cast-semantics.d.ts +7 -5
- package/dist/src/types/cast-semantics.d.ts.map +1 -1
- package/dist/src/types/cast-semantics.js +20 -9
- package/dist/src/types/cast-semantics.js.map +1 -1
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +1 -1
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/registry.d.ts.map +1 -1
- package/dist/src/types/registry.js +2 -1
- package/dist/src/types/registry.js.map +1 -1
- package/dist/src/types/temporal-ops.d.ts +119 -0
- package/dist/src/types/temporal-ops.d.ts.map +1 -0
- package/dist/src/types/temporal-ops.js +348 -0
- package/dist/src/types/temporal-ops.js.map +1 -0
- package/dist/src/types/temporal-types.d.ts +11 -0
- package/dist/src/types/temporal-types.d.ts.map +1 -1
- package/dist/src/types/temporal-types.js +87 -0
- package/dist/src/types/temporal-types.js.map +1 -1
- package/dist/src/util/affinity.d.ts +2 -1
- package/dist/src/util/affinity.d.ts.map +1 -1
- package/dist/src/util/affinity.js +16 -5
- package/dist/src/util/affinity.js.map +1 -1
- package/dist/src/util/ast-spine-clone.d.ts +2 -2
- package/dist/src/util/ast-spine-clone.js +2 -2
- package/dist/src/util/coercion.d.ts +22 -7
- package/dist/src/util/coercion.d.ts.map +1 -1
- package/dist/src/util/coercion.js +28 -15
- package/dist/src/util/coercion.js.map +1 -1
- package/dist/src/util/comparison.d.ts +25 -0
- package/dist/src/util/comparison.d.ts.map +1 -1
- package/dist/src/util/comparison.js +25 -0
- package/dist/src/util/comparison.js.map +1 -1
- package/dist/src/util/key-tuple-codec.d.ts +12 -3
- package/dist/src/util/key-tuple-codec.d.ts.map +1 -1
- package/dist/src/util/key-tuple-codec.js.map +1 -1
- package/dist/src/util/numeric-canonical.d.ts +26 -0
- package/dist/src/util/numeric-canonical.d.ts.map +1 -0
- package/dist/src/util/numeric-canonical.js +66 -0
- package/dist/src/util/numeric-canonical.js.map +1 -0
- package/dist/src/util/value-text.d.ts +48 -0
- package/dist/src/util/value-text.d.ts.map +1 -0
- package/dist/src/util/value-text.js +35 -0
- package/dist/src/util/value-text.js.map +1 -0
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +14 -5
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/dist/src/vtab/module.d.ts +25 -0
- package/dist/src/vtab/module.d.ts.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,1225 @@
|
|
|
1
|
+
import { spineCloneAst } from '../../util/ast-spine-clone.js';
|
|
2
|
+
import { eq, objectRefKeySchema, rewriteEach } from './shared.js';
|
|
3
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
4
|
+
// Column rename
|
|
5
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
6
|
+
/**
|
|
7
|
+
* Rewrite column references inside a full statement/expression AST, resolving
|
|
8
|
+
* unqualified refs against the FROM scopes the walk descends (no implicit seed,
|
|
9
|
+
* unlike {@link renameColumnInCheckExpression}). The walk descends a select
|
|
10
|
+
* body's trailing `with defaults (…)` clause ({@link AST.SelectStmt.defaults}),
|
|
11
|
+
* whose entry exprs evaluate in the body's FROM scope.
|
|
12
|
+
*
|
|
13
|
+
* `rowImage` says what a bare, unbound `new.` / `old.` qualifier means in the
|
|
14
|
+
* walked expression — independent of the (absent) seed; see
|
|
15
|
+
* {@link RowImageContext} and {@link matchesRowImage}. A view / assertion body
|
|
16
|
+
* is `'none'` (no written-row context — `new.x` is an ordinary table
|
|
17
|
+
* reference); another table's CHECK / DEFAULT / generated body is `'foreign'`
|
|
18
|
+
* (written-row context whose image belongs to that table, so the qualifier
|
|
19
|
+
* names nothing this walk cares about).
|
|
20
|
+
*
|
|
21
|
+
* When `resolveColumnInSource` is supplied, the scope walk consults it at each
|
|
22
|
+
* inner FROM frame so an unqualified ref that legitimately binds to a like-named
|
|
23
|
+
* column on a subquery's own FROM source is NOT false-captured by an enclosing
|
|
24
|
+
* binding (e.g. a `with defaults` expr `cap + (select max(cap) from lim)` under
|
|
25
|
+
* a `t.cap` rename must leave the inner `lim.cap` untouched). The same callback
|
|
26
|
+
* infrastructure backs {@link renameColumnInCheckExpression}; both the forward
|
|
27
|
+
* propagation (live schema lookup) and the differ's inverse reconcile
|
|
28
|
+
* (declared-side resolver) pass it so the two stay in parity.
|
|
29
|
+
*/
|
|
30
|
+
export function renameColumnInAst(node, tableName, oldColName, newColName, resolve, targetKey, rowImage, resolveColumnInSource) {
|
|
31
|
+
if (!node)
|
|
32
|
+
return false;
|
|
33
|
+
const state = {
|
|
34
|
+
tableName: tableName.toLowerCase(),
|
|
35
|
+
oldCol: oldColName.toLowerCase(),
|
|
36
|
+
newCol: newColName,
|
|
37
|
+
resolveRef: resolve,
|
|
38
|
+
targetKey,
|
|
39
|
+
scopeStack: [],
|
|
40
|
+
changed: false,
|
|
41
|
+
resolveColumnInSource,
|
|
42
|
+
rowImage,
|
|
43
|
+
};
|
|
44
|
+
visitColumnRename(node, state);
|
|
45
|
+
return state.changed;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Rewrite a column reference inside a CHECK expression. Unlike
|
|
49
|
+
* `renameColumnInAst`, this entry point seeds the scope stack with an
|
|
50
|
+
* implicit unaliased binding to `tableName` so top-level unqualified
|
|
51
|
+
* `ColumnExpr` nodes resolve to the owning table. CHECK expressions
|
|
52
|
+
* cannot reference other tables at top level, so the implicit binding
|
|
53
|
+
* is safe there.
|
|
54
|
+
*
|
|
55
|
+
* When `resolveColumnInSource` is supplied, the scope walk consults it at
|
|
56
|
+
* each inner FROM frame: if any real-table source in that frame exposes
|
|
57
|
+
* `oldColName`, the unqualified ref binds inside the subquery and the
|
|
58
|
+
* walk stops before reaching the seed. This stops the rewriter from
|
|
59
|
+
* false-positively rewriting an inner unqualified ref that legitimately
|
|
60
|
+
* binds to a like-named column on the subquery's FROM (e.g.
|
|
61
|
+
* `check ((select min(v) from u) > 0)` when `u` also has a `v` column).
|
|
62
|
+
*
|
|
63
|
+
* Limitation: subquery / function-source inner sources are not asked (the
|
|
64
|
+
* rewriter would need recursive column-set inference on their bodies), so an
|
|
65
|
+
* unqualified ref binding only through one of those falls through to the seed.
|
|
66
|
+
* An ALIASED real source is asked like any other — an alias adds a qualifier,
|
|
67
|
+
* it does not take the source's columns out of scope. `renameColumnInAst`
|
|
68
|
+
* shares the same callback (passed by the view-body callers) and the same
|
|
69
|
+
* residual.
|
|
70
|
+
*
|
|
71
|
+
* The seed and the `new.` / `old.` **row-image** namespace are INDEPENDENT: the
|
|
72
|
+
* seed answers how *unqualified* refs bind, while the explicit `rowImage`
|
|
73
|
+
* argument answers what a bare, unbound `new.` / `old.` qualifier names (see
|
|
74
|
+
* {@link RowImageContext}). A CHECK / DEFAULT / generated body of the renamed
|
|
75
|
+
* table itself passes `'own'` — those expressions evaluate against the row
|
|
76
|
+
* being written, so `check (new.a > 0)` (`docs/sql-ddl.md` § CHECK Constraints)
|
|
77
|
+
* names the owning table's row image and matches. A partial-index predicate is
|
|
78
|
+
* seeded too but passes `'none'`: it describes rows already stored, with no
|
|
79
|
+
* written-row context.
|
|
80
|
+
*
|
|
81
|
+
* The `'own'` match is scope-**aware**, not depth-blind, because `new` and `old`
|
|
82
|
+
* are not reserved words in this parser: `create table "new" (…)` is legal, so a
|
|
83
|
+
* CHECK may legitimately contain `(select max("new".a) from "new")`. The match
|
|
84
|
+
* therefore requires that no FROM/WITH frame above the seed rebind the
|
|
85
|
+
* qualifier — see `matchesRowImage`.
|
|
86
|
+
*/
|
|
87
|
+
export function renameColumnInCheckExpression(expr, tableName, oldColName, newColName, resolve, targetKey, rowImage, resolveColumnInSource) {
|
|
88
|
+
if (!expr)
|
|
89
|
+
return false;
|
|
90
|
+
const state = {
|
|
91
|
+
tableName: tableName.toLowerCase(),
|
|
92
|
+
oldCol: oldColName.toLowerCase(),
|
|
93
|
+
newCol: newColName,
|
|
94
|
+
resolveRef: resolve,
|
|
95
|
+
targetKey,
|
|
96
|
+
scopeStack: [],
|
|
97
|
+
changed: false,
|
|
98
|
+
resolveColumnInSource,
|
|
99
|
+
rowImage,
|
|
100
|
+
};
|
|
101
|
+
const frame = emptyFrame();
|
|
102
|
+
frame.qualifiers.set(state.tableName, state.targetKey);
|
|
103
|
+
state.scopeStack.push(frame);
|
|
104
|
+
try {
|
|
105
|
+
visitColumnRename(expr, state);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
state.scopeStack.pop();
|
|
109
|
+
}
|
|
110
|
+
return state.changed;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Sentinel rename target for the read-only column probes: the two below, and
|
|
114
|
+
* the drop verb's republication fixpoint
|
|
115
|
+
* (`schema/column-republication.ts`), which probes exposure the same way. No
|
|
116
|
+
* user column can hold this name, and the probe rewrites *to* it, so it can
|
|
117
|
+
* never collide with anything the walk is looking for.
|
|
118
|
+
*/
|
|
119
|
+
export const PROBE_COLUMN_NAME = '__quereus_column_probe__';
|
|
120
|
+
/**
|
|
121
|
+
* Whether `node` refers to `tableName`.`columnName` — read-only with respect to
|
|
122
|
+
* the caller's AST; a throwaway {@link spineCloneAst} copy is what gets
|
|
123
|
+
* rewritten.
|
|
124
|
+
*
|
|
125
|
+
* Same equivalence {@link tableReferencedInAst} establishes for the table verb,
|
|
126
|
+
* and for the same reason: "refers to" must mean "would have been rewritten by
|
|
127
|
+
* `ALTER TABLE … RENAME COLUMN`", or a DROP guard built on it refuses a
|
|
128
|
+
* different set of cases than the rename follows. See that function's comment
|
|
129
|
+
* for why the column verb reaches it by clone+sentinel rather than by `dryRun`.
|
|
130
|
+
*
|
|
131
|
+
* `resolveColumnInSource` is **not optional in practice**: without it the walk
|
|
132
|
+
* has no way to tell that an unqualified ref inside a subquery binds to a
|
|
133
|
+
* like-named column on the subquery's own FROM source, and a caller asking
|
|
134
|
+
* "does this body name `t.v`?" gets `true` for a body whose only `v` is
|
|
135
|
+
* another table's. Every caller should pass the catalog-backed resolver
|
|
136
|
+
* (`buildColumnSourceResolver`).
|
|
137
|
+
*
|
|
138
|
+
* NOTE: one spine clone plus one walk per probe, and the DROP COLUMN guard probes every
|
|
139
|
+
* live assertion. Trivial at the handful-of-assertions scale schemas have today, and it
|
|
140
|
+
* only runs on DDL; if a schema ever carries assertions by the hundred, filter by
|
|
141
|
+
* `tableReferencedInAst` (no clone) before paying for the column probe.
|
|
142
|
+
*/
|
|
143
|
+
export function columnReferencedInAst(node, tableName, columnName, resolve, targetKey, rowImage, resolveColumnInSource) {
|
|
144
|
+
if (!node)
|
|
145
|
+
return false;
|
|
146
|
+
return renameColumnInAst(spineCloneAst(node), tableName, columnName, PROBE_COLUMN_NAME, resolve, targetKey, rowImage, resolveColumnInSource);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* {@link columnReferencedInAst} for an expression that resolves unqualified refs
|
|
150
|
+
* against an implicit binding to `tableName` — a CHECK expression or a
|
|
151
|
+
* partial-index predicate. Uses {@link renameColumnInCheckExpression}'s seeded
|
|
152
|
+
* entry point so the probe answers for exactly the scope that expression is
|
|
153
|
+
* planned in.
|
|
154
|
+
*/
|
|
155
|
+
export function columnReferencedInCheckExpression(expr, tableName, columnName, resolve, targetKey, rowImage, resolveColumnInSource) {
|
|
156
|
+
if (!expr)
|
|
157
|
+
return false;
|
|
158
|
+
return renameColumnInCheckExpression(spineCloneAst(expr), tableName, columnName, PROBE_COLUMN_NAME, resolve, targetKey, rowImage, resolveColumnInSource);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Rewrite the renamed column inside every partial-index predicate of `indexes`,
|
|
162
|
+
* in place. A predicate resolves unqualified refs against the indexed table
|
|
163
|
+
* itself — the same implicit seed a CHECK expression uses — so
|
|
164
|
+
* {@link renameColumnInCheckExpression} is the correct entry point here, not
|
|
165
|
+
* {@link renameColumnInAst}.
|
|
166
|
+
*
|
|
167
|
+
* The predicate `Expression` is shared by reference between the catalog's
|
|
168
|
+
* `TableSchema`, any module-local copy of it, and — for a unique partial index —
|
|
169
|
+
* the `derivedFromIndex` UNIQUE constraint that carries the same predicate.
|
|
170
|
+
* Rewriting in place keeps all of them in step; cloning would strand the derived
|
|
171
|
+
* constraint on the old AST.
|
|
172
|
+
*
|
|
173
|
+
* Idempotent: once rewritten, nothing names `oldColName` any more, so a second
|
|
174
|
+
* call with the same pair returns false without touching the AST.
|
|
175
|
+
*
|
|
176
|
+
* The parameter is structurally typed rather than `IndexSchema[]` so this module
|
|
177
|
+
* stays free of catalog imports; `IndexSchema` satisfies it.
|
|
178
|
+
*
|
|
179
|
+
* Row-image mode is `'none'`: a partial-index predicate describes rows already
|
|
180
|
+
* stored, so it has no written-row context and a bare `new.` / `old.` qualifier
|
|
181
|
+
* there is an ordinary table reference — the same asymmetry
|
|
182
|
+
* {@link import('./table-rename.js').renameTableInIndexPredicates} records by
|
|
183
|
+
* passing no `rowImageContext`. (An indexed table literally named `new` still
|
|
184
|
+
* matches through the seed binding, so this changes no answer for the case
|
|
185
|
+
* that plans.)
|
|
186
|
+
*/
|
|
187
|
+
export function renameColumnInIndexPredicates(indexes, tableName, oldColName, newColName, resolve, targetKey, resolveColumnInSource) {
|
|
188
|
+
return rewriteEach(indexes, idx => idx.predicate, expr => renameColumnInCheckExpression(expr, tableName, oldColName, newColName, resolve, targetKey, 'none', resolveColumnInSource));
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Rewrite the renamed column inside every CHECK constraint expression of `checks`
|
|
192
|
+
* belonging to the renamed table itself, in place. The seeded-scope entry point
|
|
193
|
+
* applies for the same reason it does for a partial-index predicate: a CHECK
|
|
194
|
+
* resolves unqualified refs against its owning table.
|
|
195
|
+
*
|
|
196
|
+
* Only pass the renamed table's OWN checks — a CHECK on a *different* table that
|
|
197
|
+
* happens to reference the renamed table needs {@link renameColumnInAst}, whose
|
|
198
|
+
* walk has no implicit seed. That own-table contract is also why the row-image
|
|
199
|
+
* mode is hardcoded `'own'` here: a CHECK evaluates against the row being
|
|
200
|
+
* written, whose image IS the renamed table's.
|
|
201
|
+
*/
|
|
202
|
+
export function renameColumnInCheckConstraints(checks, tableName, oldColName, newColName, resolve, targetKey, resolveColumnInSource) {
|
|
203
|
+
return rewriteEach(checks, cc => cc.expr, expr => renameColumnInCheckExpression(expr, tableName, oldColName, newColName, resolve, targetKey, 'own', resolveColumnInSource));
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Rewrite the renamed column inside the two expressions a `ColumnSchema` can carry —
|
|
207
|
+
* a column DEFAULT (`b integer default (new.a + 1)`, however authored: inline, or via
|
|
208
|
+
* `ALTER TABLE … ALTER COLUMN … SET DEFAULT`, which writes the same field) and a
|
|
209
|
+
* generated column's body (`g integer generated always as (a + 1)`) — in place, for
|
|
210
|
+
* every column of the renamed table itself.
|
|
211
|
+
*
|
|
212
|
+
* The seeded {@link renameColumnInCheckExpression} entry point with row-image mode
|
|
213
|
+
* `'own'` is correct for both:
|
|
214
|
+
*
|
|
215
|
+
* - The implicit unaliased binding to the owning table is what makes a generated
|
|
216
|
+
* column's **bare** `a` resolve — it has no FROM clause to bind against, exactly like
|
|
217
|
+
* a CHECK.
|
|
218
|
+
* - The `'own'` mode is what makes a default's `new.a` resolve. A default is evaluated
|
|
219
|
+
* against the row being written, so that qualifier names this table's row image, not
|
|
220
|
+
* anything a FROM binds.
|
|
221
|
+
*
|
|
222
|
+
* Case folding (`NEW.A`) and the shadowing edge (a real table literally named `new`,
|
|
223
|
+
* reached from a subquery inside the expression) therefore behave exactly as they do for
|
|
224
|
+
* a CHECK, because the same walk decides all three.
|
|
225
|
+
*
|
|
226
|
+
* Only pass the renamed table's OWN columns. A default on a *different* table can reach
|
|
227
|
+
* this table only through a subquery, so an unqualified ref there must bind inside that
|
|
228
|
+
* subquery's FROM — that caller wants {@link renameColumnInAst}, whose walk has no seed.
|
|
229
|
+
*
|
|
230
|
+
* Same sharing and idempotence story as {@link renameColumnInCheckConstraints}: a
|
|
231
|
+
* module's rename hook rebuilds only the renamed column's `ColumnSchema` and keeps every
|
|
232
|
+
* other one by reference, and even the rebuilt one carries the SAME expression nodes
|
|
233
|
+
* (`buildConstraintsFromColumn` passes them into the reconstructed `ColumnDef` by
|
|
234
|
+
* reference and `columnDefToSchema` assigns them straight back), so one in-place rewrite
|
|
235
|
+
* reaches every holder and a second call with the same pair is a no-op.
|
|
236
|
+
*
|
|
237
|
+
* The parameter is structurally typed rather than `ColumnSchema[]` so this module stays
|
|
238
|
+
* free of catalog imports; `ColumnSchema` satisfies it.
|
|
239
|
+
*/
|
|
240
|
+
export function renameColumnInColumnExpressions(columns, tableName, oldColName, newColName, resolve, targetKey, resolveColumnInSource) {
|
|
241
|
+
const rewrite = (expr) => renameColumnInCheckExpression(expr, tableName, oldColName, newColName, resolve, targetKey, 'own', resolveColumnInSource);
|
|
242
|
+
// Both walks always run — `||` on the results, not short-circuited between them.
|
|
243
|
+
const defaultsChanged = rewriteEach(columns, c => c.defaultValue ?? undefined, rewrite);
|
|
244
|
+
const generatedChanged = rewriteEach(columns, c => c.generatedExpr, rewrite);
|
|
245
|
+
return defaultsChanged || generatedChanged;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Whether `body`'s top-level result columns publish the renamed column under
|
|
249
|
+
* `newColName` — i.e. whether rewriting this body shifted the name its readers
|
|
250
|
+
* see. Call AFTER the body rewrite: the predicate compares against the NEW
|
|
251
|
+
* name, exactly as the CTE exposure analysis it shares its internals with
|
|
252
|
+
* ({@link cteExposesRenamedColumn} / `isResultColumnExposure`). An explicit
|
|
253
|
+
* result-column alias suppresses exposure; a bare projection of the renamed
|
|
254
|
+
* column, or a `*` / `t.*` / `a.*` whose frame binds the target, exposes it —
|
|
255
|
+
* a FROM alias is no obstacle, since it only changes which qualifier names the
|
|
256
|
+
* source.
|
|
257
|
+
*
|
|
258
|
+
* An explicit view / materialized-view column list pins the published names
|
|
259
|
+
* regardless of the body — that guard belongs at the call site
|
|
260
|
+
* (`ViewSchema.columns` / `TableDerivation.columns` play the role
|
|
261
|
+
* `cte.columns` plays inside the CTE analysis; this predicate never sees them).
|
|
262
|
+
*/
|
|
263
|
+
export function bodyExposesRenamedColumn(body, tableName, oldColName, newColName, resolve, targetKey, resolveColumnInSource) {
|
|
264
|
+
if (!body)
|
|
265
|
+
return false;
|
|
266
|
+
const state = {
|
|
267
|
+
tableName: tableName.toLowerCase(),
|
|
268
|
+
oldCol: oldColName.toLowerCase(),
|
|
269
|
+
newCol: newColName,
|
|
270
|
+
resolveRef: resolve,
|
|
271
|
+
targetKey,
|
|
272
|
+
scopeStack: [],
|
|
273
|
+
changed: false,
|
|
274
|
+
resolveColumnInSource,
|
|
275
|
+
// Exposure is a question about published output names, not row images —
|
|
276
|
+
// `'none'` unconditionally (a `new.`-qualified projection is never a bare
|
|
277
|
+
// passthrough, so the mode cannot matter to the answer).
|
|
278
|
+
rowImage: 'none',
|
|
279
|
+
};
|
|
280
|
+
return queryExposesRenamedColumn(body, state);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Sentinel target key for {@link bodyPublishesColumnNamed}'s throwaway walk
|
|
284
|
+
* state: the frame machinery wants a rename target, but this probe must match
|
|
285
|
+
* nothing — real object keys are `<schema>.<name>` built from identifiers,
|
|
286
|
+
* which never contain a NUL.
|
|
287
|
+
*/
|
|
288
|
+
const PROBE_TARGET_KEY = '\u0000.\u0000';
|
|
289
|
+
/**
|
|
290
|
+
* Whether `body`'s top-level result columns publish a column named
|
|
291
|
+
* `columnName`, from ANY origin: an explicit alias, an unaliased bare
|
|
292
|
+
* projection of a column so named (wherever it binds), or a `*` / `t.*` whose
|
|
293
|
+
* bound real-table source exposes it (asked through `resolveColumnInSource`).
|
|
294
|
+
*
|
|
295
|
+
* The collision half of the rename-cascade pre-flight
|
|
296
|
+
* (`runtime/emit/column-rename-cascade.ts`): run against the PRISTINE
|
|
297
|
+
* (pre-rewrite) body with `columnName = newCol` — the rename target cannot
|
|
298
|
+
* publish `newCol` before the rename, so anything found is necessarily a
|
|
299
|
+
* DIFFERENT column the shifted name would collide with.
|
|
300
|
+
*
|
|
301
|
+
* Shares the walker's scope residual: a `*` over a subquery or function source
|
|
302
|
+
* has no askable column set, so a publication arriving only that way is not
|
|
303
|
+
* seen. A `*` over an aliased REAL source is seen — the alias binds like any
|
|
304
|
+
* other qualifier.
|
|
305
|
+
*/
|
|
306
|
+
export function bodyPublishesColumnNamed(body, columnName, resolve, resolveColumnInSource) {
|
|
307
|
+
if (!body || body.type !== 'select')
|
|
308
|
+
return false;
|
|
309
|
+
const select = body;
|
|
310
|
+
const nameLower = columnName.toLowerCase();
|
|
311
|
+
const state = {
|
|
312
|
+
tableName: '\u0000',
|
|
313
|
+
oldCol: '\u0000',
|
|
314
|
+
newCol: '\u0000',
|
|
315
|
+
resolveRef: resolve,
|
|
316
|
+
targetKey: PROBE_TARGET_KEY,
|
|
317
|
+
scopeStack: [],
|
|
318
|
+
changed: false,
|
|
319
|
+
resolveColumnInSource,
|
|
320
|
+
rowImage: 'none',
|
|
321
|
+
};
|
|
322
|
+
const withFrame = analyzeWithFrame(select.withClause, state);
|
|
323
|
+
state.scopeStack.push(withFrame);
|
|
324
|
+
try {
|
|
325
|
+
const frame = buildScopeFrame(select.from, state);
|
|
326
|
+
for (const col of select.columns ?? []) {
|
|
327
|
+
if (resultColumnPublishesName(col, frame, nameLower, resolveColumnInSource))
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
state.scopeStack.pop();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/** One result column's published-name test for {@link bodyPublishesColumnNamed}. */
|
|
337
|
+
function resultColumnPublishesName(col, frame, nameLower, resolveColumnInSource) {
|
|
338
|
+
if (col.type === 'all') {
|
|
339
|
+
if (!resolveColumnInSource)
|
|
340
|
+
return false;
|
|
341
|
+
if (col.table === undefined) {
|
|
342
|
+
return frame.realSources.some(src => resolveColumnInSource(src.schemaLower, src.nameLower, nameLower));
|
|
343
|
+
}
|
|
344
|
+
const key = frame.qualifiers.get(col.table.toLowerCase());
|
|
345
|
+
if (key === undefined)
|
|
346
|
+
return false;
|
|
347
|
+
return frame.realSources.some(src => src.key === key
|
|
348
|
+
&& resolveColumnInSource(src.schemaLower, src.nameLower, nameLower));
|
|
349
|
+
}
|
|
350
|
+
if (col.alias !== undefined)
|
|
351
|
+
return col.alias.toLowerCase() === nameLower;
|
|
352
|
+
return col.expr.type === 'column' && col.expr.name.toLowerCase() === nameLower;
|
|
353
|
+
}
|
|
354
|
+
function emptyFrame() {
|
|
355
|
+
return {
|
|
356
|
+
qualifiers: new Map(),
|
|
357
|
+
ctesExposingRenamed: new Set(),
|
|
358
|
+
ctesInScope: new Set(),
|
|
359
|
+
ctesShadowingSource: new Set(),
|
|
360
|
+
realSources: [],
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function buildScopeFrame(from, state) {
|
|
364
|
+
const frame = emptyFrame();
|
|
365
|
+
if (!from)
|
|
366
|
+
return frame;
|
|
367
|
+
for (const item of from) {
|
|
368
|
+
collectFromBindings(item, state, frame);
|
|
369
|
+
}
|
|
370
|
+
return frame;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Bind a FROM source's one qualifier in `frame`, FIRST-write-wins.
|
|
374
|
+
*
|
|
375
|
+
* The order only matters when two sources in one FROM claim the same qualifier
|
|
376
|
+
* (`from t join u as t`), which is legal here — the planner accepts it and
|
|
377
|
+
* resolves a qualified ref to the FIRST such source in FROM order (`select t.k
|
|
378
|
+
* from t join u as t` reads `t`; `from u as t join t` reads `u`; a BARE `k` is
|
|
379
|
+
* rejected as ambiguous either way). Measured against the engine, not assumed.
|
|
380
|
+
* Skipping the overwrite is what keeps {@link resolveQualifierBinding} agreeing
|
|
381
|
+
* with that, now that one map holds aliases and bare names together — a
|
|
382
|
+
* two-map lookup used to answer alias-first, which matched the planner only
|
|
383
|
+
* when the aliased source came first.
|
|
384
|
+
*
|
|
385
|
+
* First-wins matches how the planner resolves a qualified COLUMN, but the planner
|
|
386
|
+
* expands a qualified STAR by name-matching every source, so `select t.* from u as
|
|
387
|
+
* t join t` yields both sources' columns while this walk sees only `u` — the
|
|
388
|
+
* engine disagreeing with itself about a duplicated qualifier, tracked by
|
|
389
|
+
* `bug-duplicate-from-qualifier-resolves-inconsistently`. Making this walk match
|
|
390
|
+
* the star would just pick the other side of the disagreement; the ticket's fix is
|
|
391
|
+
* to reject the duplicate at build time, after which the question stops existing.
|
|
392
|
+
*/
|
|
393
|
+
function bindQualifier(frame, qualifierLower, key) {
|
|
394
|
+
if (!frame.qualifiers.has(qualifierLower))
|
|
395
|
+
frame.qualifiers.set(qualifierLower, key);
|
|
396
|
+
}
|
|
397
|
+
function collectFromBindings(item, state, frame) {
|
|
398
|
+
switch (item.type) {
|
|
399
|
+
case 'table': {
|
|
400
|
+
const ts = item;
|
|
401
|
+
const name = ts.table.name.toLowerCase();
|
|
402
|
+
// Unqualified reference to a CTE in scope — the CTE shadows any
|
|
403
|
+
// same-named real table. Whether it re-exposes the renamed column
|
|
404
|
+
// determines whether unqualified refs against this source rewrite.
|
|
405
|
+
if (ts.table.schema === undefined && isCteInScope(state, name)) {
|
|
406
|
+
if (isCteExposingInScope(state, name)) {
|
|
407
|
+
// One qualifier either way — the alias when present, the CTE's
|
|
408
|
+
// own name otherwise — and it both binds unqualified refs and
|
|
409
|
+
// serves as a qualifier for refs like `a.k`.
|
|
410
|
+
bindQualifier(frame, ts.alias ? ts.alias.toLowerCase() : name, state.targetKey);
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
// Shadowing-but-not-exposing: the source binds to the CTE
|
|
414
|
+
// row source, not the renamed real table. Record the
|
|
415
|
+
// qualifier (alias if present, otherwise the source name)
|
|
416
|
+
// so qualified column refs against it don't short-circuit
|
|
417
|
+
// to the renamed table.
|
|
418
|
+
frame.ctesShadowingSource.add(ts.alias ? ts.alias.toLowerCase() : name);
|
|
419
|
+
}
|
|
420
|
+
// Shadowing-but-not-exposing: do not bind as the renamed table.
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
// Resolve the source the way the planner would for this body: home
|
|
424
|
+
// schema path for an unqualified name, passthrough for a qualified
|
|
425
|
+
// one. The KEY is what every capture / qualifier decision compares —
|
|
426
|
+
// a bare `from t` binds the renamed table only when it RESOLVES to
|
|
427
|
+
// it, and `from other_schema.t z` never does just because the bare
|
|
428
|
+
// names collide.
|
|
429
|
+
const key = state.resolveRef(ts.table.schema, ts.table.name);
|
|
430
|
+
if (key === undefined)
|
|
431
|
+
break;
|
|
432
|
+
// The source's ONE qualifier: its alias when it has one, its bare
|
|
433
|
+
// name otherwise. Either way it binds unqualified refs — an alias
|
|
434
|
+
// does not take the source's columns out of scope — and either way
|
|
435
|
+
// an aliased source stops answering to its bare name (`select t.x
|
|
436
|
+
// from t a` does not plan, so `t` must not register).
|
|
437
|
+
bindQualifier(frame, ts.alias ? ts.alias.toLowerCase() : name, key);
|
|
438
|
+
// Record as a real-table source (under its RESOLVED schema) so the
|
|
439
|
+
// unqualified-scope walk can ask whether this source exposes the
|
|
440
|
+
// renamed column. Both aliased and unaliased real sources are
|
|
441
|
+
// recorded — asking "does u expose col v" is the same question
|
|
442
|
+
// regardless of any alias.
|
|
443
|
+
frame.realSources.push({ key, schemaLower: objectRefKeySchema(key, name), nameLower: name });
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
case 'join': {
|
|
447
|
+
const join = item;
|
|
448
|
+
collectFromBindings(join.left, state, frame);
|
|
449
|
+
collectFromBindings(join.right, state, frame);
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
case 'subquerySource':
|
|
453
|
+
case 'functionSource':
|
|
454
|
+
// No askable column set without recursive inference on the inner
|
|
455
|
+
// body, so these register no qualifier and no real source — the
|
|
456
|
+
// residual tracked by
|
|
457
|
+
// `bug-column-verbs-blind-to-star-over-subquery-source`.
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Innermost-first walk: an inner non-exposing same-name CTE shadows an
|
|
463
|
+
* outer exposing one, so a `ctesInScope` hit without a matching
|
|
464
|
+
* `ctesExposingRenamed` entry wins. `isCteInScope` (below) intentionally
|
|
465
|
+
* stays OR-shaped — it only gates "is this source a CTE rather than a
|
|
466
|
+
* real table?", a question for which any enclosing CTE suffices.
|
|
467
|
+
*/
|
|
468
|
+
function isCteExposingInScope(state, name) {
|
|
469
|
+
for (let i = state.scopeStack.length - 1; i >= 0; i--) {
|
|
470
|
+
const frame = state.scopeStack[i];
|
|
471
|
+
if (frame.ctesExposingRenamed.has(name))
|
|
472
|
+
return true;
|
|
473
|
+
if (frame.ctesInScope.has(name))
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
function isCteInScope(state, name) {
|
|
479
|
+
for (const frame of state.scopeStack) {
|
|
480
|
+
if (frame.ctesInScope.has(name))
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Whether a BARE (unqualified) column ref at the current position binds the
|
|
487
|
+
* renamed table. Innermost-first: a closer same-name CTE shadows an outer
|
|
488
|
+
* binding to the renamed real table. When a `resolveColumnInSource` callback is
|
|
489
|
+
* configured, also stop at any inner FROM frame whose real sources expose
|
|
490
|
+
* `oldCol` — the unqualified ref binds inside that frame and an outer seed
|
|
491
|
+
* binding must not capture it.
|
|
492
|
+
*
|
|
493
|
+
* Aliased and unaliased sources bind alike here, and the shadow scan over
|
|
494
|
+
* `realSources` widens in step (it records both), so `from u b` still stops an
|
|
495
|
+
* outer capture of a bare `x` that `u` exposes.
|
|
496
|
+
*/
|
|
497
|
+
function unqualifiedRefBindsTarget(state) {
|
|
498
|
+
for (let i = state.scopeStack.length - 1; i >= 0; i--) {
|
|
499
|
+
const frame = state.scopeStack[i];
|
|
500
|
+
if (frame.ctesInScope.has(state.tableName))
|
|
501
|
+
return false;
|
|
502
|
+
if (state.resolveColumnInSource && frame.realSources.length > 0) {
|
|
503
|
+
for (const src of frame.realSources) {
|
|
504
|
+
// The renamed table itself trivially exposes oldCol; defer to
|
|
505
|
+
// the binding check below so we don't double-capture.
|
|
506
|
+
if (src.key === state.targetKey)
|
|
507
|
+
continue;
|
|
508
|
+
if (state.resolveColumnInSource(src.schemaLower, src.nameLower, state.oldCol))
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (frameBindsTarget(frame, state.targetKey))
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
/** Whether any source bound in `frame` resolves to `targetKey`. */
|
|
518
|
+
function frameBindsTarget(frame, targetKey) {
|
|
519
|
+
for (const key of frame.qualifiers.values()) {
|
|
520
|
+
if (key === targetKey)
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Resolve a bare column qualifier innermost-first against the scope stack:
|
|
527
|
+
* the resolved key of the table it binds (through an alias, an unaliased
|
|
528
|
+
* source, or an exposing CTE), `null` when it binds something that is not the
|
|
529
|
+
* renamed real table and can never be (a non-exposing shadowing CTE, or a CTE
|
|
530
|
+
* merely in scope), or `undefined` when nothing in scope binds it. The
|
|
531
|
+
* innermost binding decides — standard SQL shadowing. Subquery / function
|
|
532
|
+
* sources are not recorded (pre-existing limitation, shared with the
|
|
533
|
+
* unqualified capture walk), so a qualifier bound only by one of those falls
|
|
534
|
+
* through to the caller's seedless fallback.
|
|
535
|
+
*
|
|
536
|
+
* A qualifier claimed TWICE inside one FROM (`from t join u as t`) is legal and
|
|
537
|
+
* resolves to the FIRST such source; {@link bindQualifier} is what makes that
|
|
538
|
+
* true here, and it is a small IMPROVEMENT on the two-map lookup this replaced
|
|
539
|
+
* (which answered alias-first, and so read `from t join u as t` as `u` where
|
|
540
|
+
* the planner reads it as `t`).
|
|
541
|
+
*/
|
|
542
|
+
function resolveQualifierBinding(state, qualifierLower) {
|
|
543
|
+
for (let i = state.scopeStack.length - 1; i >= 0; i--) {
|
|
544
|
+
const frame = state.scopeStack[i];
|
|
545
|
+
const bound = frame.qualifiers.get(qualifierLower);
|
|
546
|
+
if (bound !== undefined)
|
|
547
|
+
return bound;
|
|
548
|
+
if (frame.ctesShadowingSource.has(qualifierLower))
|
|
549
|
+
return null;
|
|
550
|
+
if (frame.ctesInScope.has(qualifierLower))
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
return undefined;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Whether a qualified column ref names a **row image** — `new.<col>` /
|
|
557
|
+
* `old.<col>` in written-row context — and if so, whose. The three-way answer
|
|
558
|
+
* mirrors {@link RowImageContext}:
|
|
559
|
+
*
|
|
560
|
+
* - `'own'`: the image is the walk's target table — the ref matches and the
|
|
561
|
+
* rename rewrites it (`check (new.a > 0)` on the renamed table itself).
|
|
562
|
+
* - `'foreign'`: written-row context, but the image belongs to some OTHER
|
|
563
|
+
* relation (another table's CHECK / DEFAULT / generated body, or a
|
|
564
|
+
* `with inverse` expression whose `new.` names the enclosing select's output
|
|
565
|
+
* row) — the ref names nothing this walk cares about and must be IGNORED,
|
|
566
|
+
* not resolved as a table called `new`.
|
|
567
|
+
* - `'none'`: no written-row context here — the qualifier is not a row image
|
|
568
|
+
* at all, and the caller falls through to ordinary table-reference
|
|
569
|
+
* resolution.
|
|
570
|
+
*
|
|
571
|
+
* A schema-qualified `main.new.a` is a real three-part table reference, never a
|
|
572
|
+
* row image, so it answers `'none'` outright, as does any qualifier a FROM/WITH
|
|
573
|
+
* frame rebinds (`new` and `old` are not reserved words — `create table "new"`
|
|
574
|
+
* is legal, so `(select max("new".a) from "new")` must keep resolving as a
|
|
575
|
+
* table). Callers reach this only for a qualifier nothing in scope binds, which
|
|
576
|
+
* makes the rebind scan redundant there — kept as a cheap belt-and-braces guard
|
|
577
|
+
* so this function is safe to call from any future site.
|
|
578
|
+
*
|
|
579
|
+
* `qualifierLower` is `col.table` already lowercased by the caller.
|
|
580
|
+
*/
|
|
581
|
+
function matchesRowImage(state, col, qualifierLower) {
|
|
582
|
+
if (state.rowImage === 'none')
|
|
583
|
+
return 'none';
|
|
584
|
+
if (col.schema !== undefined)
|
|
585
|
+
return 'none';
|
|
586
|
+
if (qualifierLower !== 'new' && qualifierLower !== 'old')
|
|
587
|
+
return 'none';
|
|
588
|
+
if (isQualifierReboundAboveSeed(state, qualifierLower))
|
|
589
|
+
return 'none';
|
|
590
|
+
return state.rowImage;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Whether any real FROM / WITH frame rebinds `qualifier` to a row source of its
|
|
594
|
+
* own, which would make a `new.` / `old.` ref name that source rather than the
|
|
595
|
+
* row image. `new` and `old` are not reserved words here — `create table "new"`
|
|
596
|
+
* is legal — so this scan is what stops the row-image match from false-firing
|
|
597
|
+
* inside `check ((select max("new".a) from "new") >= 0)`.
|
|
598
|
+
*
|
|
599
|
+
* The scan starts at index 1: frame 0 is the implicit seed
|
|
600
|
+
* {@link renameColumnInCheckExpression} pushes for the owning table, which is
|
|
601
|
+
* not a FROM binding and must not count as a rebind. All three ways a frame can
|
|
602
|
+
* bind a qualifier are checked — a FROM source's own qualifier (its alias, or
|
|
603
|
+
* its bare name when unaliased), a CTE name in scope, and a
|
|
604
|
+
* shadowing-but-not-exposing CTE source.
|
|
605
|
+
*
|
|
606
|
+
* Deliberately "any enclosing frame wins" rather than innermost-first: a frame
|
|
607
|
+
* only sits above index 0 while the walk is *inside* the subquery that pushed
|
|
608
|
+
* it, so every such frame really does enclose the reference. A top-level
|
|
609
|
+
* `new.a` in `check (new.a > 0 and (select count(*) from "new") >= 0)` is
|
|
610
|
+
* visited with only the seed on the stack and still matches.
|
|
611
|
+
*
|
|
612
|
+
* NOTE: the residual is a *correlated* `new.<col>` written inside a subquery
|
|
613
|
+
* that selects from a real table named `new` — left alone by both the rewrite
|
|
614
|
+
* and the drop refusal, since the qualifier is ambiguous there anyway. If that
|
|
615
|
+
* ever needs to resolve to the row image, it needs a spelling that distinguishes
|
|
616
|
+
* the two (the SQL standard has none), not a change to this scan.
|
|
617
|
+
*/
|
|
618
|
+
function isQualifierReboundAboveSeed(state, qualifier) {
|
|
619
|
+
for (let i = 1; i < state.scopeStack.length; i++) {
|
|
620
|
+
const frame = state.scopeStack[i];
|
|
621
|
+
if (frame.qualifiers.has(qualifier))
|
|
622
|
+
return true;
|
|
623
|
+
if (frame.ctesInScope.has(qualifier))
|
|
624
|
+
return true;
|
|
625
|
+
if (frame.ctesShadowingSource.has(qualifier))
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Visit a subtree that is written-row context belonging to the ENCLOSING select
|
|
632
|
+
* — a `with inverse` assignment expression or a `with defaults` entry
|
|
633
|
+
* expression — with the row-image mode forced to `'foreign'`, restoring the
|
|
634
|
+
* ambient mode after. The override is unconditional (not `||`-combined with the
|
|
635
|
+
* ambient mode): even inside a seeded `'own'` walk, a nested select's inverse
|
|
636
|
+
* expression's `new.` refs name THAT select's outputs, not the seeded table's
|
|
637
|
+
* row image.
|
|
638
|
+
*/
|
|
639
|
+
function visitRowImageForeignSubtree(expr, state) {
|
|
640
|
+
const ambient = state.rowImage;
|
|
641
|
+
state.rowImage = 'foreign';
|
|
642
|
+
try {
|
|
643
|
+
visitColumnRename(expr, state);
|
|
644
|
+
}
|
|
645
|
+
finally {
|
|
646
|
+
state.rowImage = ambient;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function visitColumnRename(node, state) {
|
|
650
|
+
if (!node)
|
|
651
|
+
return;
|
|
652
|
+
switch (node.type) {
|
|
653
|
+
case 'select': {
|
|
654
|
+
const stmt = node;
|
|
655
|
+
pushWithFrame(stmt.withClause, state);
|
|
656
|
+
try {
|
|
657
|
+
const frame = buildScopeFrame(stmt.from, state);
|
|
658
|
+
state.scopeStack.push(frame);
|
|
659
|
+
try {
|
|
660
|
+
// Capture pre-rewrite output names of UNALIASED bare projections: a
|
|
661
|
+
// rename that rewrites one shifts the select's OUTPUT name with it, so
|
|
662
|
+
// any `new.<old>` refs in sibling `with inverse` exprs must follow
|
|
663
|
+
// (a `new.` ref is by view-output name; aliased / computed projections
|
|
664
|
+
// keep their output name, so the body rewrite alone covers them).
|
|
665
|
+
const preOutputNames = (stmt.columns ?? []).map(c => c.type === 'column' && !c.alias && c.expr.type === 'column' ? c.expr.name : undefined);
|
|
666
|
+
(stmt.columns ?? []).forEach(c => {
|
|
667
|
+
if (c.type === 'column')
|
|
668
|
+
visitColumnRename(c.expr, state);
|
|
669
|
+
});
|
|
670
|
+
// `with inverse` clauses: the assignment target is a bare base-column
|
|
671
|
+
// name resolving against this select's FROM — exactly an unqualified
|
|
672
|
+
// body ref, so it rides the same scope-aware walk via a synthetic
|
|
673
|
+
// probe (the `with defaults` clause below uses the same pattern). The
|
|
674
|
+
// assignment EXPR is written-row context regardless of the ambient
|
|
675
|
+
// mode: its `new.<x>` refs name the enclosing select's OUTPUT row
|
|
676
|
+
// (docs/sql-select.md § Result-column inverses), never any rename
|
|
677
|
+
// target's row image and never a table called `new` — so the walk
|
|
678
|
+
// descends it with the mode forced to `'foreign'` (the legitimate
|
|
679
|
+
// output-name shift is applied separately by
|
|
680
|
+
// {@link renameNewQualifiedRefs} after the body walk). The table
|
|
681
|
+
// walker forces its own suppression over the same subtree.
|
|
682
|
+
(stmt.columns ?? []).forEach(c => {
|
|
683
|
+
if (c.type !== 'column' || !c.inverse?.length)
|
|
684
|
+
return;
|
|
685
|
+
c.inverse.forEach(a => {
|
|
686
|
+
const probe = { type: 'column', name: a.column };
|
|
687
|
+
visitColumnRename(probe, state);
|
|
688
|
+
if (probe.name !== a.column) {
|
|
689
|
+
a.column = probe.name;
|
|
690
|
+
state.changed = true;
|
|
691
|
+
}
|
|
692
|
+
visitRowImageForeignSubtree(a.expr, state);
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
// `with defaults` clause: each entry's `column` is a bare base-column
|
|
696
|
+
// name of this select's FROM (a projected-away base column), so it
|
|
697
|
+
// rides the same scope-aware synthetic probe as a `with inverse`
|
|
698
|
+
// target; the entry's `expr` evaluates in the FROM frame already on
|
|
699
|
+
// the scope stack, so it rewrites like any body expression (an inner
|
|
700
|
+
// subquery in the expr pushes its own frame and disambiguates a
|
|
701
|
+
// like-named column). A defaults entry is documented self-contained —
|
|
702
|
+
// it cannot reference the inserted row (docs/sql-select.md § Insert
|
|
703
|
+
// defaults) — but nothing enforces that at CREATE VIEW time, so the
|
|
704
|
+
// walk pins the documented meaning the same way the inverse arm above
|
|
705
|
+
// does: a bare unbound `new.` / `old.` there is inert, never a
|
|
706
|
+
// reference to a table so named (such a ref could not plan anyway —
|
|
707
|
+
// the lowered INSERT gives the expr no scope that binds `new`).
|
|
708
|
+
(stmt.defaults ?? []).forEach(d => {
|
|
709
|
+
const probe = { type: 'column', name: d.column };
|
|
710
|
+
visitColumnRename(probe, state);
|
|
711
|
+
if (probe.name !== d.column) {
|
|
712
|
+
d.column = probe.name;
|
|
713
|
+
state.changed = true;
|
|
714
|
+
}
|
|
715
|
+
visitRowImageForeignSubtree(d.expr, state);
|
|
716
|
+
});
|
|
717
|
+
const outputRenames = new Map();
|
|
718
|
+
(stmt.columns ?? []).forEach((c, i) => {
|
|
719
|
+
const before = preOutputNames[i];
|
|
720
|
+
if (before !== undefined && c.type === 'column' && c.expr.type === 'column' && c.expr.name !== before) {
|
|
721
|
+
outputRenames.set(before.toLowerCase(), c.expr.name);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
// A star projection covering the renamed table exposes the old column
|
|
725
|
+
// name as an OUTPUT name too — the rename shifts it exactly like an
|
|
726
|
+
// unaliased bare projection, so sibling `new.<old>` refs must follow.
|
|
727
|
+
// Skipped when an explicit projection still exposes the old name
|
|
728
|
+
// (first-occurrence resolution keeps `new.<old>` bound to it).
|
|
729
|
+
const hasInverseClauses = (stmt.columns ?? []).some(c => c.type === 'column' && !!c.inverse?.length);
|
|
730
|
+
if (hasInverseClauses && !outputRenames.has(state.oldCol)) {
|
|
731
|
+
const starCoversRenamed = (stmt.columns ?? []).some(c => {
|
|
732
|
+
if (c.type !== 'all')
|
|
733
|
+
return false;
|
|
734
|
+
if (c.table === undefined)
|
|
735
|
+
return frameBindsTarget(frame, state.targetKey);
|
|
736
|
+
return frame.qualifiers.get(c.table.toLowerCase()) === state.targetKey;
|
|
737
|
+
});
|
|
738
|
+
const oldStillExposed = (stmt.columns ?? []).some(c => c.type === 'column'
|
|
739
|
+
&& (c.alias
|
|
740
|
+
? c.alias.toLowerCase() === state.oldCol
|
|
741
|
+
: c.expr.type === 'column' && c.expr.name.toLowerCase() === state.oldCol));
|
|
742
|
+
if (starCoversRenamed && !oldStillExposed) {
|
|
743
|
+
outputRenames.set(state.oldCol, state.newCol);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (outputRenames.size > 0) {
|
|
747
|
+
(stmt.columns ?? []).forEach(c => {
|
|
748
|
+
if (c.type !== 'column' || !c.inverse?.length)
|
|
749
|
+
return;
|
|
750
|
+
c.inverse.forEach(a => {
|
|
751
|
+
if (renameNewQualifiedRefs(a.expr, outputRenames))
|
|
752
|
+
state.changed = true;
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
(stmt.from ?? []).forEach(f => visitColumnRename(f, state));
|
|
757
|
+
visitColumnRename(stmt.where, state);
|
|
758
|
+
(stmt.groupBy ?? []).forEach(g => visitColumnRename(g, state));
|
|
759
|
+
visitColumnRename(stmt.having, state);
|
|
760
|
+
(stmt.orderBy ?? []).forEach(o => visitColumnRename(o.expr, state));
|
|
761
|
+
visitColumnRename(stmt.limit, state);
|
|
762
|
+
visitColumnRename(stmt.offset, state);
|
|
763
|
+
visitColumnRename(stmt.union, state);
|
|
764
|
+
if (stmt.compound)
|
|
765
|
+
visitColumnRename(stmt.compound.select, state);
|
|
766
|
+
}
|
|
767
|
+
finally {
|
|
768
|
+
state.scopeStack.pop();
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
finally {
|
|
772
|
+
state.scopeStack.pop();
|
|
773
|
+
}
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
case 'insert': {
|
|
777
|
+
const stmt = node;
|
|
778
|
+
pushWithFrame(stmt.withClause, state);
|
|
779
|
+
try {
|
|
780
|
+
// Mirrors the planner's write-target rule (and the table walker's
|
|
781
|
+
// `visitDmlTarget`): an unqualified target naming a member of the
|
|
782
|
+
// statement's own leading WITH binds that CTE, not the renamed table.
|
|
783
|
+
const targetIsCte = stmt.table.schema === undefined &&
|
|
784
|
+
(stmt.withClause?.ctes ?? []).some(c => eq(c.name, stmt.table.name));
|
|
785
|
+
const targetIsRenamed = !targetIsCte &&
|
|
786
|
+
state.resolveRef(stmt.table.schema, stmt.table.name) === state.targetKey;
|
|
787
|
+
if (targetIsRenamed && stmt.columns) {
|
|
788
|
+
stmt.columns = stmt.columns.map(c => {
|
|
789
|
+
if (c.toLowerCase() === state.oldCol) {
|
|
790
|
+
state.changed = true;
|
|
791
|
+
return state.newCol;
|
|
792
|
+
}
|
|
793
|
+
return c;
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
if (targetIsRenamed) {
|
|
797
|
+
(stmt.upsertClauses ?? []).forEach(uc => {
|
|
798
|
+
if (uc.conflictTarget) {
|
|
799
|
+
uc.conflictTarget = uc.conflictTarget.map(c => {
|
|
800
|
+
if (c.toLowerCase() === state.oldCol) {
|
|
801
|
+
state.changed = true;
|
|
802
|
+
return state.newCol;
|
|
803
|
+
}
|
|
804
|
+
return c;
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
if (uc.assignments) {
|
|
808
|
+
for (const a of uc.assignments) {
|
|
809
|
+
if (a.column.toLowerCase() === state.oldCol) {
|
|
810
|
+
a.column = state.newCol;
|
|
811
|
+
state.changed = true;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
visitColumnRename(stmt.source, state);
|
|
818
|
+
(stmt.upsertClauses ?? []).forEach(uc => {
|
|
819
|
+
(uc.assignments ?? []).forEach(a => visitColumnRename(a.value, state));
|
|
820
|
+
visitColumnRename(uc.where, state);
|
|
821
|
+
});
|
|
822
|
+
(stmt.returning ?? []).forEach(r => {
|
|
823
|
+
if (r.type === 'column')
|
|
824
|
+
visitColumnRename(r.expr, state);
|
|
825
|
+
});
|
|
826
|
+
(stmt.contextValues ?? []).forEach(cv => visitColumnRename(cv.value, state));
|
|
827
|
+
}
|
|
828
|
+
finally {
|
|
829
|
+
state.scopeStack.pop();
|
|
830
|
+
}
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
833
|
+
case 'update': {
|
|
834
|
+
const stmt = node;
|
|
835
|
+
pushWithFrame(stmt.withClause, state);
|
|
836
|
+
try {
|
|
837
|
+
// Same write-target rule as the insert arm above.
|
|
838
|
+
const targetIsCte = stmt.table.schema === undefined &&
|
|
839
|
+
(stmt.withClause?.ctes ?? []).some(c => eq(c.name, stmt.table.name));
|
|
840
|
+
const targetIsRenamed = !targetIsCte &&
|
|
841
|
+
state.resolveRef(stmt.table.schema, stmt.table.name) === state.targetKey;
|
|
842
|
+
if (targetIsRenamed) {
|
|
843
|
+
for (const a of stmt.assignments) {
|
|
844
|
+
if (a.column.toLowerCase() === state.oldCol) {
|
|
845
|
+
a.column = state.newCol;
|
|
846
|
+
state.changed = true;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
// Push a scope frame so unqualified column refs in WHERE/RETURNING
|
|
851
|
+
// resolve against the update target, bound under its resolved key.
|
|
852
|
+
const frame = emptyFrame();
|
|
853
|
+
const targetBinding = state.resolveRef(stmt.table.schema, stmt.table.name);
|
|
854
|
+
if (targetBinding !== undefined) {
|
|
855
|
+
frame.qualifiers.set(stmt.table.name.toLowerCase(), targetBinding);
|
|
856
|
+
}
|
|
857
|
+
state.scopeStack.push(frame);
|
|
858
|
+
try {
|
|
859
|
+
stmt.assignments.forEach(a => visitColumnRename(a.value, state));
|
|
860
|
+
visitColumnRename(stmt.where, state);
|
|
861
|
+
(stmt.returning ?? []).forEach(r => {
|
|
862
|
+
if (r.type === 'column')
|
|
863
|
+
visitColumnRename(r.expr, state);
|
|
864
|
+
});
|
|
865
|
+
(stmt.contextValues ?? []).forEach(cv => visitColumnRename(cv.value, state));
|
|
866
|
+
}
|
|
867
|
+
finally {
|
|
868
|
+
state.scopeStack.pop();
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
finally {
|
|
872
|
+
state.scopeStack.pop();
|
|
873
|
+
}
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
case 'delete': {
|
|
877
|
+
const stmt = node;
|
|
878
|
+
pushWithFrame(stmt.withClause, state);
|
|
879
|
+
try {
|
|
880
|
+
const frame = emptyFrame();
|
|
881
|
+
const targetBinding = state.resolveRef(stmt.table.schema, stmt.table.name);
|
|
882
|
+
if (targetBinding !== undefined) {
|
|
883
|
+
frame.qualifiers.set(stmt.table.name.toLowerCase(), targetBinding);
|
|
884
|
+
}
|
|
885
|
+
state.scopeStack.push(frame);
|
|
886
|
+
try {
|
|
887
|
+
visitColumnRename(stmt.where, state);
|
|
888
|
+
(stmt.returning ?? []).forEach(r => {
|
|
889
|
+
if (r.type === 'column')
|
|
890
|
+
visitColumnRename(r.expr, state);
|
|
891
|
+
});
|
|
892
|
+
(stmt.contextValues ?? []).forEach(cv => visitColumnRename(cv.value, state));
|
|
893
|
+
}
|
|
894
|
+
finally {
|
|
895
|
+
state.scopeStack.pop();
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
finally {
|
|
899
|
+
state.scopeStack.pop();
|
|
900
|
+
}
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
case 'values': {
|
|
904
|
+
const stmt = node;
|
|
905
|
+
stmt.values.forEach(row => row.forEach(v => visitColumnRename(v, state)));
|
|
906
|
+
break;
|
|
907
|
+
}
|
|
908
|
+
case 'join': {
|
|
909
|
+
const join = node;
|
|
910
|
+
visitColumnRename(join.left, state);
|
|
911
|
+
visitColumnRename(join.right, state);
|
|
912
|
+
visitColumnRename(join.condition, state);
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
case 'functionSource': {
|
|
916
|
+
node.args.forEach(a => visitColumnRename(a, state));
|
|
917
|
+
break;
|
|
918
|
+
}
|
|
919
|
+
case 'subquerySource': {
|
|
920
|
+
visitColumnRename(node.subquery, state);
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
case 'binary': {
|
|
924
|
+
const e = node;
|
|
925
|
+
visitColumnRename(e.left, state);
|
|
926
|
+
visitColumnRename(e.right, state);
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
929
|
+
case 'unary':
|
|
930
|
+
case 'cast':
|
|
931
|
+
case 'collate':
|
|
932
|
+
visitColumnRename(node.expr, state);
|
|
933
|
+
break;
|
|
934
|
+
case 'function':
|
|
935
|
+
node.args.forEach(a => visitColumnRename(a, state));
|
|
936
|
+
break;
|
|
937
|
+
case 'subquery':
|
|
938
|
+
visitColumnRename(node.query, state);
|
|
939
|
+
break;
|
|
940
|
+
case 'windowFunction': {
|
|
941
|
+
const wf = node;
|
|
942
|
+
visitColumnRename(wf.function, state);
|
|
943
|
+
visitColumnRename(wf.window, state);
|
|
944
|
+
break;
|
|
945
|
+
}
|
|
946
|
+
case 'windowDefinition': {
|
|
947
|
+
const wd = node;
|
|
948
|
+
(wd.partitionBy ?? []).forEach(p => visitColumnRename(p, state));
|
|
949
|
+
(wd.orderBy ?? []).forEach(o => visitColumnRename(o.expr, state));
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
case 'case': {
|
|
953
|
+
const ce = node;
|
|
954
|
+
visitColumnRename(ce.baseExpr, state);
|
|
955
|
+
ce.whenThenClauses.forEach(wt => {
|
|
956
|
+
visitColumnRename(wt.when, state);
|
|
957
|
+
visitColumnRename(wt.then, state);
|
|
958
|
+
});
|
|
959
|
+
visitColumnRename(ce.elseExpr, state);
|
|
960
|
+
break;
|
|
961
|
+
}
|
|
962
|
+
case 'in': {
|
|
963
|
+
const ie = node;
|
|
964
|
+
visitColumnRename(ie.expr, state);
|
|
965
|
+
(ie.values ?? []).forEach(v => visitColumnRename(v, state));
|
|
966
|
+
visitColumnRename(ie.subquery, state);
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
969
|
+
case 'exists':
|
|
970
|
+
visitColumnRename(node.subquery, state);
|
|
971
|
+
break;
|
|
972
|
+
case 'between': {
|
|
973
|
+
const be = node;
|
|
974
|
+
visitColumnRename(be.expr, state);
|
|
975
|
+
visitColumnRename(be.lower, state);
|
|
976
|
+
visitColumnRename(be.upper, state);
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
case 'column': {
|
|
980
|
+
const col = node;
|
|
981
|
+
if (col.name.toLowerCase() !== state.oldCol)
|
|
982
|
+
break;
|
|
983
|
+
if (col.table) {
|
|
984
|
+
const qualifierLower = col.table.toLowerCase();
|
|
985
|
+
let hit;
|
|
986
|
+
if (col.schema !== undefined) {
|
|
987
|
+
// A schema-qualified qualifier (`main.t.k`) can never be an
|
|
988
|
+
// alias, a CTE, or the row image — resolve it directly.
|
|
989
|
+
hit = state.resolveRef(col.schema, col.table) === state.targetKey;
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
const binding = resolveQualifierBinding(state, qualifierLower);
|
|
993
|
+
if (binding !== undefined) {
|
|
994
|
+
// The innermost binding decides: the renamed table's key,
|
|
995
|
+
// another object's key, or null (a shadowing CTE). A table
|
|
996
|
+
// genuinely named `new` / `old` that is bound in scope —
|
|
997
|
+
// the seeded owning table included — resolves through its
|
|
998
|
+
// binding, never through the row image.
|
|
999
|
+
hit = binding === state.targetKey;
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
// An UNBOUND bare qualifier: the three-way row-image mode
|
|
1003
|
+
// decides (see {@link matchesRowImage}) — `'own'` matches
|
|
1004
|
+
// the walk's target, `'foreign'` is another relation's row
|
|
1005
|
+
// image and must be ignored (NOT resolved as a table called
|
|
1006
|
+
// `new`), `'none'` falls through to direct resolution, as
|
|
1007
|
+
// the planner would against the body's home schema path
|
|
1008
|
+
// (correlation the walk cannot see).
|
|
1009
|
+
const image = matchesRowImage(state, col, qualifierLower);
|
|
1010
|
+
hit = image === 'own' ? true
|
|
1011
|
+
: image === 'foreign' ? false
|
|
1012
|
+
: state.resolveRef(undefined, col.table) === state.targetKey;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
if (hit) {
|
|
1016
|
+
col.name = state.newCol;
|
|
1017
|
+
state.changed = true;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
if (unqualifiedRefBindsTarget(state)) {
|
|
1022
|
+
col.name = state.newCol;
|
|
1023
|
+
state.changed = true;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
break;
|
|
1027
|
+
}
|
|
1028
|
+
case 'table':
|
|
1029
|
+
// Table sources don't contain column names.
|
|
1030
|
+
break;
|
|
1031
|
+
default:
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Push a with-frame that registers any CTEs in the given WITH clause that
|
|
1037
|
+
* re-expose the renamed column. CTEs are visited in declaration order so
|
|
1038
|
+
* later CTEs see earlier ones in the same WITH.
|
|
1039
|
+
*
|
|
1040
|
+
* For `with recursive`, each CTE's name is registered in `ctesInScope`
|
|
1041
|
+
* *before* its body is visited so self-references inside the recursive step
|
|
1042
|
+
* resolve to the CTE (not to a same-named renamed table). For non-recursive
|
|
1043
|
+
* WITH, the name is registered only after the body — a non-recursive body
|
|
1044
|
+
* must not see itself.
|
|
1045
|
+
*
|
|
1046
|
+
* Caller is responsible for popping the frame via `state.scopeStack.pop()`.
|
|
1047
|
+
*/
|
|
1048
|
+
function pushWithFrame(withClause, state) {
|
|
1049
|
+
const frame = emptyFrame();
|
|
1050
|
+
state.scopeStack.push(frame);
|
|
1051
|
+
if (withClause) {
|
|
1052
|
+
for (const cte of withClause.ctes) {
|
|
1053
|
+
const nameLower = cte.name.toLowerCase();
|
|
1054
|
+
if (withClause.recursive) {
|
|
1055
|
+
frame.ctesInScope.add(nameLower);
|
|
1056
|
+
}
|
|
1057
|
+
visitColumnRename(cte.query, state);
|
|
1058
|
+
if (cteExposesRenamedColumn(cte, state)) {
|
|
1059
|
+
frame.ctesExposingRenamed.add(nameLower);
|
|
1060
|
+
}
|
|
1061
|
+
frame.ctesInScope.add(nameLower);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
return frame;
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Rebuild a with-frame's `ctesExposingRenamed` set for exposure analysis
|
|
1068
|
+
* without re-visiting CTE bodies (they were already visited).
|
|
1069
|
+
*/
|
|
1070
|
+
function analyzeWithFrame(withClause, state) {
|
|
1071
|
+
const frame = emptyFrame();
|
|
1072
|
+
if (!withClause)
|
|
1073
|
+
return frame;
|
|
1074
|
+
state.scopeStack.push(frame);
|
|
1075
|
+
try {
|
|
1076
|
+
for (const cte of withClause.ctes) {
|
|
1077
|
+
if (cteExposesRenamedColumn(cte, state)) {
|
|
1078
|
+
frame.ctesExposingRenamed.add(cte.name.toLowerCase());
|
|
1079
|
+
}
|
|
1080
|
+
frame.ctesInScope.add(cte.name.toLowerCase());
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
finally {
|
|
1084
|
+
state.scopeStack.pop();
|
|
1085
|
+
}
|
|
1086
|
+
return frame;
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Determine whether a CTE re-exposes the renamed column under name `state.newCol`
|
|
1090
|
+
* (the column has already been rewritten inside its body if the body referenced it).
|
|
1091
|
+
*
|
|
1092
|
+
* Returns false when:
|
|
1093
|
+
* - The CTE has an explicit column list (renaming the input to fixed names).
|
|
1094
|
+
* - The body is not a SELECT (INSERT/UPDATE/DELETE WITH RETURNING — out of scope).
|
|
1095
|
+
* - No passthrough result column references the renamed table's column.
|
|
1096
|
+
*/
|
|
1097
|
+
function cteExposesRenamedColumn(cte, state) {
|
|
1098
|
+
if (cte.columns)
|
|
1099
|
+
return false;
|
|
1100
|
+
return queryExposesRenamedColumn(cte.query, state);
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* The query-level core {@link cteExposesRenamedColumn} and the public
|
|
1104
|
+
* {@link bodyExposesRenamedColumn} share: whether `query`'s top-level result
|
|
1105
|
+
* columns publish the renamed column under `state.newCol`. Expects the body to
|
|
1106
|
+
* be ALREADY rewritten — the walk rewrites a CTE body before asking, and the
|
|
1107
|
+
* public entry point documents the same contract. For a compound body the
|
|
1108
|
+
* first arm's result list is what names the output, and that is what
|
|
1109
|
+
* `query.columns` holds here; a non-select body never exposes.
|
|
1110
|
+
*/
|
|
1111
|
+
function queryExposesRenamedColumn(query, state) {
|
|
1112
|
+
if (query.type !== 'select')
|
|
1113
|
+
return false;
|
|
1114
|
+
const select = query;
|
|
1115
|
+
// Recreate the body's own with-frame so nested CTE refs in `select.from`
|
|
1116
|
+
// resolve correctly during exposure analysis.
|
|
1117
|
+
const bodyWithFrame = analyzeWithFrame(select.withClause, state);
|
|
1118
|
+
state.scopeStack.push(bodyWithFrame);
|
|
1119
|
+
try {
|
|
1120
|
+
const bodyFrame = buildScopeFrame(select.from, state);
|
|
1121
|
+
for (const col of select.columns ?? []) {
|
|
1122
|
+
if (isResultColumnExposure(col, bodyFrame, state))
|
|
1123
|
+
return true;
|
|
1124
|
+
}
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
1127
|
+
finally {
|
|
1128
|
+
state.scopeStack.pop();
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
function isResultColumnExposure(col, bodyFrame, state) {
|
|
1132
|
+
if (col.type === 'all') {
|
|
1133
|
+
if (col.table === undefined) {
|
|
1134
|
+
return frameBindsTarget(bodyFrame, state.targetKey);
|
|
1135
|
+
}
|
|
1136
|
+
return bodyFrame.qualifiers.get(col.table.toLowerCase()) === state.targetKey;
|
|
1137
|
+
}
|
|
1138
|
+
if (col.alias !== undefined)
|
|
1139
|
+
return false;
|
|
1140
|
+
const expr = col.expr;
|
|
1141
|
+
if (expr.type !== 'column')
|
|
1142
|
+
return false;
|
|
1143
|
+
const colExpr = expr;
|
|
1144
|
+
if (colExpr.name.toLowerCase() !== state.newCol.toLowerCase())
|
|
1145
|
+
return false;
|
|
1146
|
+
if (colExpr.table === undefined) {
|
|
1147
|
+
return frameBindsTarget(bodyFrame, state.targetKey);
|
|
1148
|
+
}
|
|
1149
|
+
if (colExpr.schema !== undefined) {
|
|
1150
|
+
// Schema-qualified: never an alias — resolve directly.
|
|
1151
|
+
return state.resolveRef(colExpr.schema, colExpr.table) === state.targetKey;
|
|
1152
|
+
}
|
|
1153
|
+
return bodyFrame.qualifiers.get(colExpr.table.toLowerCase()) === state.targetKey;
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Rename `new.<old>` → `new.<new>` references inside a `with inverse` assignment
|
|
1157
|
+
* expression, for output columns whose name shifted under a column rename (an
|
|
1158
|
+
* unaliased bare projection of the renamed column). Uniform, depth-blind in-place
|
|
1159
|
+
* walk over the expression's object graph: the `new.` qualifier alone decides (it
|
|
1160
|
+
* is the reserved written-row namespace no FROM source legitimately shadows), so
|
|
1161
|
+
* no scope tracking applies — narrower and simpler than the scope-aware walkers
|
|
1162
|
+
* above. Returns whether any reference was rewritten.
|
|
1163
|
+
*
|
|
1164
|
+
* Depth-blind **on purpose**, unlike the row-image match in `matchesRowImage`.
|
|
1165
|
+
* The two look alike but answer different questions: this one rewrites by view
|
|
1166
|
+
* *output* name inside a `with inverse` expr, so no FROM scope tracking applies
|
|
1167
|
+
* (`new.` is the written-row namespace validation reserves there —
|
|
1168
|
+
* `planner/analysis/authored-inverse.ts` requires every `new.<x>` to resolve to
|
|
1169
|
+
* an output column of its own enclosing select), whereas a CHECK body may
|
|
1170
|
+
* contain a subquery selecting from a real table named `"new"` and so must
|
|
1171
|
+
* consult the scope stack.
|
|
1172
|
+
*
|
|
1173
|
+
* One boundary the depth-blind walk must still respect: a subquery inside the
|
|
1174
|
+
* expression may be a select carrying its OWN `with inverse` clause, whose
|
|
1175
|
+
* `new.` refs name THAT select's outputs (validation pins them there — they can
|
|
1176
|
+
* never reach the outer select's), so the walk skips `inverse` subtrees.
|
|
1177
|
+
* `inverse` is a property of {@link AST.ResultColumnExpr} only, and the walk's
|
|
1178
|
+
* roots are the OUTER clause's assignment exprs, so every `inverse` key met
|
|
1179
|
+
* in-walk belongs to a nested select. Correlated `new.<outer-output>` refs in
|
|
1180
|
+
* ORDINARY subqueries of the expression are still reached — the skip is the
|
|
1181
|
+
* clause, not the subquery.
|
|
1182
|
+
*
|
|
1183
|
+
* NOTE: `inverse` is skipped but `defaults` is not, deliberately — a nested
|
|
1184
|
+
* select's `with defaults` entry expr owns no written-row scope (the inverse
|
|
1185
|
+
* validation in `planner/analysis/authored-inverse.ts` never descends one, so a
|
|
1186
|
+
* `new.` ref there binds nothing either way) and shifting a ref inside one is
|
|
1187
|
+
* inert. If `with defaults` ever gains a written-row scope of its own, this walk
|
|
1188
|
+
* needs the same skip for `defaults`.
|
|
1189
|
+
*/
|
|
1190
|
+
function renameNewQualifiedRefs(expr, renames) {
|
|
1191
|
+
let changed = false;
|
|
1192
|
+
const visit = (v) => {
|
|
1193
|
+
if (Array.isArray(v)) {
|
|
1194
|
+
v.forEach(visit);
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
if (v === null || typeof v !== 'object')
|
|
1198
|
+
return;
|
|
1199
|
+
const n = v;
|
|
1200
|
+
if (n.type === 'column' && typeof n.table === 'string' && n.table.toLowerCase() === 'new'
|
|
1201
|
+
&& n.schema === undefined && typeof n.name === 'string') {
|
|
1202
|
+
const to = renames.get(n.name.toLowerCase());
|
|
1203
|
+
if (to !== undefined && to !== n.name) {
|
|
1204
|
+
n.name = to;
|
|
1205
|
+
changed = true;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
for (const key of Object.keys(n)) {
|
|
1209
|
+
if (key === 'loc' || key === 'inverse')
|
|
1210
|
+
continue;
|
|
1211
|
+
visit(n[key]);
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
visit(expr);
|
|
1215
|
+
return changed;
|
|
1216
|
+
}
|
|
1217
|
+
// The former `renameTableInInsertDefaults` / `renameColumnInInsertDefaults`
|
|
1218
|
+
// standalone clause rewriters are gone: `with defaults (…)` now rides inside the
|
|
1219
|
+
// select body (`SelectStmt.defaults`), so the body walks above
|
|
1220
|
+
// (`visitTableRename` / `visitColumnRename` select cases) descend it directly —
|
|
1221
|
+
// the entry `expr` rewrites in the select's FROM scope frame and the entry
|
|
1222
|
+
// `column` target rides the same scope-aware synthetic probe as a `with inverse`
|
|
1223
|
+
// target. No seeded CHECK-expr scope / declared resolver is needed: the real
|
|
1224
|
+
// FROM frame is on the scope stack, exactly as for any body reference.
|
|
1225
|
+
//# sourceMappingURL=column-rename.js.map
|