1515package analyzer
1616
1717import (
18+ "strings"
19+
1820 "github.com/cockroachdb/errors"
1921 "github.com/dolthub/go-mysql-server/sql"
2022 "github.com/dolthub/go-mysql-server/sql/analyzer"
@@ -33,9 +35,9 @@ import (
3335// by examining all rows, following PostgreSQL's type resolution rules.
3436// This ensures VALUES(1),(2.01),(3) correctly infers numeric type, not integer.
3537func ResolveValuesTypes (ctx * sql.Context , a * analyzer.Analyzer , node sql.Node , scope * plan.Scope , selector analyzer.RuleSelector , qFlags * sql.QueryFlags ) (sql.Node , transform.TreeIdentity , error ) {
36- // Track which VDTs we transform so we can update GetField nodes
38+ // Walk the tree and wrap mixed-type VALUES columns with ImplicitCast.
39+ // We record which VDTs changed so we can fix up GetField types afterward.
3740 transformedVDTs := make (map [sql.TableId ]sql.Schema )
38- // First we transform VDTs and record their new schemas
3941 node , same , err := transform .NodeWithOpaque (node , func (n sql.Node ) (sql.Node , transform.TreeIdentity , error ) {
4042 newNode , same , err := transformValuesNode (n )
4143 if err != nil {
@@ -52,7 +54,10 @@ func ResolveValuesTypes(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, s
5254 return nil , transform .SameTree , err
5355 }
5456
55- // Next we update all GetField expressions that refer to a transformed VDT
57+ // Now, fix GetField types that reference a transformed VDT. For example,
58+ // after wrapping VALUES(1),(2.5) with ImplicitCast to numeric, any
59+ // GetField reading column "n" from that VDT still says int4 and needs
60+ // to be updated to numeric.
5661 if len (transformedVDTs ) > 0 {
5762 node , _ , err = pgtransform .NodeExprsWithOpaque (node , func (expr sql.Expression ) (sql.Expression , transform.TreeIdentity , error ) {
5863 gf , ok := expr .(* expression.GetField )
@@ -64,29 +69,74 @@ func ResolveValuesTypes(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, s
6469 return expr , transform .SameTree , nil
6570 }
6671
67- // GetField indices are 1-based in GMS planbuilder, so subtract 1 for schema access
68- schemaIdx := gf .Index () - 1
69- if schemaIdx < 0 || schemaIdx >= len (newSch ) {
70- return nil , transform .NewTree , errors .Errorf ("VALUES: GetField `%s` on table `%s` uses invalid index `%d`" ,
71- gf .Name (), gf .Table (), gf .Index ())
72+ // We match by column name because GetField indices are global
73+ // across all tables in a JOIN (e.g., a.n=0, b.id=1, b.label=2).
74+ // We can't convert a global index to a per-table position without
75+ // knowing the table's starting offset, which we don't have here.
76+ schemaIdx := - 1
77+ for i , col := range newSch {
78+ if col .Name == gf .Name () {
79+ schemaIdx = i
80+ break
81+ }
82+ }
83+ if schemaIdx < 0 {
84+ return expr , transform .SameTree , nil
7285 }
7386
7487 newType := newSch [schemaIdx ].Type
7588 if gf .Type () == newType {
7689 return expr , transform .SameTree , nil
7790 }
7891
79- // Create a new expression with the updated type
80- newGf := expression .NewGetFieldWithTable (
81- gf .Index (),
82- int (gf .TableId ()),
83- newType ,
84- gf .Database (),
85- gf .Table (),
86- gf .Name (),
87- gf .IsNullable (),
88- )
89- return newGf , transform .NewTree , nil
92+ return getFieldWithType (gf , newType ), transform .NewTree , nil
93+ })
94+ if err != nil {
95+ return nil , transform .SameTree , err
96+ }
97+
98+ // The pass above only fixed GetFields that read directly from a VDT
99+ // (matched by tableId). But changing a VDT column's type can have a
100+ // ripple effect: if that column feeds into an aggregate like MIN or
101+ // MAX, the aggregate's return type changes too. Parent nodes that
102+ // read the aggregate result still have the old type. For example:
103+ //
104+ // SELECT MIN(n) FROM (VALUES(1),(2.5)) v(n)
105+ //
106+ // Project [GetField("min(v.n)", tableId=GroupBy, type=int4)]
107+ // └── GroupBy [MIN(GetField("n", tableId=VDT, type=numeric))]
108+ // └── VDT [n: int4 → numeric]
109+ //
110+ // The pass above fixed "n" inside MIN because its tableId=VDT.
111+ // MIN now returns numeric, so GroupBy produces numeric. But the
112+ // Project's GetField still says int4 because its tableId=GroupBy,
113+ // which wasn't in transformedVDTs. At runtime this causes a panic
114+ // because the actual value is decimal.Decimal but the type says int32.
115+ //
116+ // This pass catches those: for each GetField, check if its type
117+ // disagrees with what the child node actually produces.
118+ node , _ , err = pgtransform .NodeExprsWithNodeWithOpaque (node , func (n sql.Node , expr sql.Expression ) (sql.Expression , transform.TreeIdentity , error ) {
119+ gf , ok := expr .(* expression.GetField )
120+ if ! ok {
121+ return expr , transform .SameTree , nil
122+ }
123+ // Skip VDT GetFields — the first pass already handled these
124+ if _ , isVDT := transformedVDTs [gf .TableId ()]; isVDT {
125+ return expr , transform .SameTree , nil
126+ }
127+ // Collect the schema that this node's children produce
128+ var childSchema sql.Schema
129+ for _ , child := range n .Children () {
130+ childSchema = append (childSchema , child .Schema ()... )
131+ }
132+ // Find the matching column by name and update if the type changed
133+ gfNameLower := strings .ToLower (gf .Name ())
134+ for _ , col := range childSchema {
135+ if strings .ToLower (col .Name ) == gfNameLower && gf .Type () != col .Type {
136+ return getFieldWithType (gf , col .Type ), transform .NewTree , nil
137+ }
138+ }
139+ return expr , transform .SameTree , nil
90140 })
91141 if err != nil {
92142 return nil , transform .SameTree , err
@@ -96,6 +146,19 @@ func ResolveValuesTypes(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, s
96146 return node , same , nil
97147}
98148
149+ // getFieldWithType returns a copy of the GetField with a new type.
150+ func getFieldWithType (gf * expression.GetField , newType sql.Type ) * expression.GetField {
151+ return expression .NewGetFieldWithTable (
152+ gf .Index (),
153+ int (gf .TableId ()),
154+ newType ,
155+ gf .Database (),
156+ gf .Table (),
157+ gf .Name (),
158+ gf .IsNullable (),
159+ )
160+ }
161+
99162// transformValuesNode transforms a plan.Values or plan.ValueDerivedTable node to use common types
100163func transformValuesNode (n sql.Node ) (sql.Node , transform.TreeIdentity , error ) {
101164 var values * plan.Values
@@ -170,7 +233,7 @@ func transformValuesNode(n sql.Node) (sql.Node, transform.TreeIdentity, error) {
170233 }
171234
172235 // Flatten the new tuples into a single expression slice for WithExpressions
173- var flatExprs []sql.Expression
236+ flatExprs := make ( []sql.Expression , 0 , len ( newTuples ) * len ( newTuples [ 0 ]))
174237 for _ , row := range newTuples {
175238 flatExprs = append (flatExprs , row ... )
176239 }
0 commit comments