From a46194ed5eca7c39b99560a54a6f502d5b4046f5 Mon Sep 17 00:00:00 2001 From: rebelice Date: Wed, 13 May 2026 11:50:19 +0900 Subject: [PATCH] fix(pg): resolve scalar function aliases as values --- pg/catalog/analyze.go | 66 +++++++++++++++++++---------- pg/catalog/scope_regression_test.go | 8 ++++ 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/pg/catalog/analyze.go b/pg/catalog/analyze.go index 013cf13a..15a0f77f 100644 --- a/pg/catalog/analyze.go +++ b/pg/catalog/analyze.go @@ -1382,17 +1382,7 @@ func (ac *analyzeCtx) resolveColumnRef(tableName, colName string) (*VarExpr, err } for colIdx, cn := range rte.ColNames { if cn == colName { - var coll uint32 - if colIdx < len(rte.ColCollations) { - coll = rte.ColCollations[colIdx] - } - v := &VarExpr{ - RangeIdx: rtIdx, - AttNum: int16(colIdx + 1), - TypeOID: rte.ColTypes[colIdx], - TypeMod: rte.ColTypMods[colIdx], - Collation: coll, - } + v := makeRTEColumnVar(rtIdx, rte, colIdx) if found != nil { return nil, errAmbiguousColumn(colName) } @@ -1404,6 +1394,17 @@ func (ac *analyzeCtx) resolveColumnRef(tableName, colName string) (*VarExpr, err } } + if tableName == "" && found == nil { + for rtIdx, rte := range ac.query.RangeTable { + if v := ac.makeScalarFunctionWholeRowVar(rtIdx, rte, colName); v != nil { + if found != nil { + return nil, errAmbiguousColumn(colName) + } + found = v + } + } + } + if found == nil && ac.parent != nil { // Try resolving in parent context (correlated subquery). // pg: src/backend/parser/parse_expr.c — transformColumnRef @@ -1490,17 +1491,7 @@ func (ac *analyzeCtx) findVarInRTE(rtIdx int, colName string) (*VarExpr, bool) { if cn != colName { continue } - var coll uint32 - if colIdx < len(rte.ColCollations) { - coll = rte.ColCollations[colIdx] - } - v := &VarExpr{ - RangeIdx: rtIdx, - AttNum: int16(colIdx + 1), - TypeOID: rte.ColTypes[colIdx], - TypeMod: rte.ColTypMods[colIdx], - Collation: coll, - } + v := makeRTEColumnVar(rtIdx, rte, colIdx) if found != nil { return nil, true } @@ -1509,6 +1500,37 @@ func (ac *analyzeCtx) findVarInRTE(rtIdx int, colName string) (*VarExpr, bool) { return found, false } +func makeRTEColumnVar(rtIdx int, rte *RangeTableEntry, colIdx int) *VarExpr { + var coll uint32 + if colIdx < len(rte.ColCollations) { + coll = rte.ColCollations[colIdx] + } + return &VarExpr{ + RangeIdx: rtIdx, + AttNum: int16(colIdx + 1), + TypeOID: rte.ColTypes[colIdx], + TypeMod: rte.ColTypMods[colIdx], + Collation: coll, + } +} + +func (ac *analyzeCtx) makeScalarFunctionWholeRowVar(rtIdx int, rte *RangeTableEntry, name string) *VarExpr { + if rte.Kind != RTEFunction || rte.ERef != name || rte.Ordinality || len(rte.FuncExprs) != 1 { + return nil + } + fexpr := rte.FuncExprs[0] + if fexpr == nil || ac.catalog.getTypeFuncClass(fexpr.exprType()) != typeFuncScalar { + return nil + } + return &VarExpr{ + RangeIdx: rtIdx, + AttNum: 1, + TypeOID: fexpr.exprType(), + TypeMod: -1, + Collation: fexpr.exprCollation(), + } +} + // transformAConst transforms a constant value. // // pg: src/backend/parser/parse_expr.c — make_const diff --git a/pg/catalog/scope_regression_test.go b/pg/catalog/scope_regression_test.go index b0bc0fc2..4becc656 100644 --- a/pg/catalog/scope_regression_test.go +++ b/pg/catalog/scope_regression_test.go @@ -194,3 +194,11 @@ func TestPGAnalyzerScopeRejectsNonLateralSetopSiblingReference(t *testing.T) { t.Fatal("LoadSQL unexpectedly accepted non-LATERAL set-op reference to sibling alias") } } + +func TestPGAnalyzerRangeFunctionAliasAsJsonbOperand(t *testing.T) { + c := New() + parseAndAnalyze(t, c, ` + SELECT elem ->> 'matchedDateTimeValue' + FROM jsonb_array_elements('[{}]'::jsonb) elem + `) +}