From 126279d2ea92433f490f5f525df0acbba4147d20 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Mon, 8 Dec 2025 12:10:13 +0000 Subject: [PATCH 1/6] Fix query planning for complex queries with impossible conditions. Signed-off-by: Arthur Schreiber --- .../planbuilder/operators/subquery_builder.go | 44 ++- .../planbuilder/testdata/select_cases.json | 308 +++++++++++++++++- go/vt/vtgate/semantics/semantic_table.go | 15 +- 3 files changed, 362 insertions(+), 5 deletions(-) diff --git a/go/vt/vtgate/planbuilder/operators/subquery_builder.go b/go/vt/vtgate/planbuilder/operators/subquery_builder.go index c2256df06f4..7c0de3d9823 100644 --- a/go/vt/vtgate/planbuilder/operators/subquery_builder.go +++ b/go/vt/vtgate/planbuilder/operators/subquery_builder.go @@ -169,7 +169,49 @@ func createSubquery( sqc := &SubQueryBuilder{totalID: totalID, subqID: subqID, outerID: outerID} predicates, joinCols := sqc.inspectStatement(ctx, subq.Select) - correlated := !ctx.SemTable.RecursiveDeps(subq).IsEmpty() + + subqDependencies := ctx.SemTable.RecursiveDeps(subq) + correlated := subqDependencies.KeepOnly(outerID).NotEmpty() + + opInner := translateQueryToOp(ctx, subq.Select) + + opInner = sqc.getRootOperator(opInner, nil) + return &SubQuery{ + FilterType: filterType, + Subquery: opInner, + Predicates: predicates, + Original: original, + ArgName: argName, + originalSubquery: originalSq, + IsArgument: isArg, + TopLevel: topLevel, + JoinColumns: joinCols, + correlated: correlated, + } +} + +func createSubqueryFromPath( + ctx *plancontext.PlanningContext, + original sqlparser.Expr, + subq *sqlparser.Subquery, + path sqlparser.ASTPath, + outerID semantics.TableSet, + parent sqlparser.Expr, + argName string, + filterType opcode.PulloutOpcode, + isArg bool, +) *SubQuery { + topLevel := ctx.SemTable.EqualsExpr(original, parent) + original = cloneASTAndSemState(ctx, original) + originalSq := sqlparser.GetNodeFromPath(original, path).(*sqlparser.Subquery) + subqID := findTablesContained(ctx, originalSq.Select) + totalID := subqID.Merge(outerID) + sqc := &SubQueryBuilder{totalID: totalID, subqID: subqID, outerID: outerID} + + predicates, joinCols := sqc.inspectStatement(ctx, subq.Select) + + subqDependencies := ctx.SemTable.RecursiveDeps(subq) + correlated := subqDependencies.KeepOnly(outerID).NotEmpty() opInner := translateQueryToOp(ctx, subq.Select) diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases.json b/go/vt/vtgate/planbuilder/testdata/select_cases.json index ef6df93441e..fad7b90f634 100644 --- a/go/vt/vtgate/planbuilder/testdata/select_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/select_cases.json @@ -4295,7 +4295,7 @@ ] } }, - { + { "comment": "Subquery with `IN` condition using columns with matching lookup vindexes, with inner scatter query", "query": "SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.foo = 'bar') AND music.user_id IN (3, 4, 5)", "plan": { @@ -4321,6 +4321,85 @@ ] } }, + { + "comment": "Subquery with `IN` condition using columns with matching lookup vindexes, impossible conditions and limit clause", + "query": "SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id IN (1, 2, 3) AND 1 = 0 AND music.foo = 'bar' LIMIT 0, 100) _inner)", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id IN (1, 2, 3) AND 1 = 0 AND music.foo = 'bar' LIMIT 0, 100) _inner)", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values", + "__sq1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Limit", + "Count": "100", + "Offset": "0", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from (select music.id from music where 1 != 1) as _inner where 1 != 1", + "Query": "select id from (select music.id from music where 0) as _inner limit 100" + } + ] + }, + { + "InputName": "Outer", + "OperatorType": "VindexLookup", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Values": [ + "::__sq1" + ], + "Vindex": "music_user_map", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select `name`, keyspace_id from name_user_vdx where 1 != 1", + "Query": "select `name`, keyspace_id from name_user_vdx where `name` in ::__vals", + "Values": [ + "::name" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "ByDestination", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values and music.id in ::__vals" + } + ] + } + ] + }, + "TablesUsed": [ + "user.music" + ] + } + }, { "comment": "Subquery with `IN` condition using columns with matching lookup vindexes", "query": "SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (1, 2, 3)) and music.user_id = 5", @@ -5879,8 +5958,231 @@ "Sharded": true }, "FieldQuery": "select 1 from (select id as uid from `user` where 1 != 1) as t, `user` where 1 != 1", - "Query": "select 1 from (select id as uid from `user`) as t, `user` where t.uid = `user`.id", - "Table": "`user`" + "Query": "select 1 from (select id as uid from `user`) as t, `user` where t.uid = `user`.id" + }, + "TablesUsed": [ + "user.user" + ] + } + }, + { + "comment": "Window function with IN clause - ROW_NUMBER with PARTITION BY routing column", + "query": "SELECT id, intcol, ROW_NUMBER() OVER (PARTITION BY id ORDER BY intcol) as rn FROM user WHERE id IN (1,2,3,4)", + "plan": { + "Type": "MultiShard", + "QueryType": "SELECT", + "Original": "SELECT id, intcol, ROW_NUMBER() OVER (PARTITION BY id ORDER BY intcol) as rn FROM user WHERE id IN (1,2,3,4)", + "Instructions": { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id, intcol, row_number() over (partition by id order by intcol asc) as rn from `user` where 1 != 1", + "Query": "select id, intcol, row_number() over (partition by id order by intcol asc) as rn from `user` where id in ::__vals", + "Values": [ + "(1, 2, 3, 4)" + ], + "Vindex": "user_index" + }, + "TablesUsed": [ + "user.user" + ] + } + }, + { + "comment": "UNION ALL with window functions - each branch has window function with different primary vindex values", + "query": "SELECT id, intcol, ROW_NUMBER() OVER (PARTITION BY id ORDER BY intcol) as rn FROM user WHERE id = 1 UNION ALL SELECT id, intcol, ROW_NUMBER() OVER (PARTITION BY id ORDER BY intcol) as rn FROM user WHERE id = 2", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "SELECT id, intcol, ROW_NUMBER() OVER (PARTITION BY id ORDER BY intcol) as rn FROM user WHERE id = 1 UNION ALL SELECT id, intcol, ROW_NUMBER() OVER (PARTITION BY id ORDER BY intcol) as rn FROM user WHERE id = 2", + "Instructions": { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id, intcol, row_number() over (partition by id order by intcol asc) as rn from `user` where 1 != 1", + "Query": "select id, intcol, row_number() over (partition by id order by intcol asc) as rn from `user` where id = 1", + "Values": [ + "1" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id, intcol, row_number() over (partition by id order by intcol asc) as rn from `user` where 1 != 1", + "Query": "select id, intcol, row_number() over (partition by id order by intcol asc) as rn from `user` where id = 2", + "Values": [ + "2" + ], + "Vindex": "user_index" + } + ] + }, + "TablesUsed": [ + "user.user" + ] + } + }, + { + "comment": "Window function with RANK on single shard - PARTITION BY includes primary vindex", + "query": "SELECT id, textcol1, RANK() OVER (PARTITION BY id, textcol1 ORDER BY id) as rnk FROM user WHERE id = 5", + "plan": { + "Type": "Passthrough", + "QueryType": "SELECT", + "Original": "SELECT id, textcol1, RANK() OVER (PARTITION BY id, textcol1 ORDER BY id) as rnk FROM user WHERE id = 5", + "Instructions": { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id, textcol1, rank() over (partition by id, textcol1 order by id asc) as rnk from `user` where 1 != 1", + "Query": "select id, textcol1, rank() over (partition by id, textcol1 order by id asc) as rnk from `user` where id = 5", + "Values": [ + "5" + ], + "Vindex": "user_index" + }, + "TablesUsed": [ + "user.user" + ] + } + }, + { + "comment": "Multiple window functions with same partition in single shard query", + "query": "SELECT id, intcol, ROW_NUMBER() OVER (PARTITION BY id ORDER BY intcol) as rn, RANK() OVER (PARTITION BY id ORDER BY intcol) as rnk FROM user WHERE id = 100", + "plan": { + "Type": "Passthrough", + "QueryType": "SELECT", + "Original": "SELECT id, intcol, ROW_NUMBER() OVER (PARTITION BY id ORDER BY intcol) as rn, RANK() OVER (PARTITION BY id ORDER BY intcol) as rnk FROM user WHERE id = 100", + "Instructions": { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id, intcol, row_number() over (partition by id order by intcol asc) as rn, rank() over (partition by id order by intcol asc) as rnk from `user` where 1 != 1", + "Query": "select id, intcol, row_number() over (partition by id order by intcol asc) as rn, rank() over (partition by id order by intcol asc) as rnk from `user` where id = 100", + "Values": [ + "100" + ], + "Vindex": "user_index" + }, + "TablesUsed": [ + "user.user" + ] + } + }, + { + "comment": "Window function with composite vindex (multi-column) - PARTITION BY includes all primary vindex columns", + "query": "SELECT cola, colb, colc, RANK() OVER (PARTITION BY cola, colb ORDER BY colc) as rnk FROM multicol_tbl WHERE cola = 'A' AND colb = 'B'", + "plan": { + "Type": "Passthrough", + "QueryType": "SELECT", + "Original": "SELECT cola, colb, colc, RANK() OVER (PARTITION BY cola, colb ORDER BY colc) as rnk FROM multicol_tbl WHERE cola = 'A' AND colb = 'B'", + "Instructions": { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select cola, colb, colc, rank() over (partition by cola, colb order by colc asc) as rnk from multicol_tbl where 1 != 1", + "Query": "select cola, colb, colc, rank() over (partition by cola, colb order by colc asc) as rnk from multicol_tbl where cola = 'A' and colb = 'B'", + "Values": [ + "'A'", + "'B'" + ], + "Vindex": "multicolIdx" + }, + "TablesUsed": [ + "user.multicol_tbl" + ] + } + }, + { + "comment": "window function in subquery on unsharded table", + "query": "select * from (select rank() over (partition by col) as r from unsharded) as t", + "plan": { + "Type": "Passthrough", + "QueryType": "SELECT", + "Original": "select * from (select rank() over (partition by col) as r from unsharded) as t", + "Instructions": { + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select * from (select rank() over (partition by col) as r from unsharded where 1 != 1) as t where 1 != 1", + "Query": "select * from (select rank() over (partition by col) as r from unsharded) as t" + }, + "TablesUsed": [ + "main.unsharded" + ] + } + }, + { + "comment": "window function in subquery on sharded table with single shard predicate", + "query": "select * from (select rank() over (partition by col) as r from user where id = 1) as t", + "plan": { + "Type": "Passthrough", + "QueryType": "SELECT", + "Original": "select * from (select rank() over (partition by col) as r from user where id = 1) as t", + "Instructions": { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select r from (select rank() over (partition by col) as r from `user` where 1 != 1) as t where 1 != 1", + "Query": "select r from (select rank() over (partition by col) as r from `user` where id = 1) as t", + "Values": [ + "1" + ], + "Vindex": "user_index" + }, + "TablesUsed": [ + "user.user" + ] + } + }, + { + "comment": "window function in subquery on sharded table with outer predicate", + "query": "select * from (select rank() over (partition by col) as r, id from user) as t where id = 1", + "plan": { + "Type": "Passthrough", + "QueryType": "SELECT", + "Original": "select * from (select rank() over (partition by col) as r, id from user) as t where id = 1", + "Instructions": { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select r, id from (select rank() over (partition by col) as r, id from `user` where 1 != 1) as t where 1 != 1", + "Query": "select r, id from (select rank() over (partition by col) as r, id from `user` where id = 1) as t", + "Values": [ + "1" + ], + "Vindex": "user_index" }, "TablesUsed": [ "user.user" diff --git a/go/vt/vtgate/semantics/semantic_table.go b/go/vt/vtgate/semantics/semantic_table.go index 6683fd5b2f9..94fd9ffd0cf 100644 --- a/go/vt/vtgate/semantics/semantic_table.go +++ b/go/vt/vtgate/semantics/semantic_table.go @@ -545,7 +545,20 @@ func (st *SemTable) CopySemanticInfo(from, to sqlparser.SQLNode) { if !ok { return } - st.CopyDependencies(f, t) + + // Not all expressions are valid map keys + if !ValidAsMapKey(t) || !ValidAsMapKey(f) { + return + } + + if _, ok := t.(*sqlparser.ColName); ok { + // If this is introducing a new column, we should copy all dependencies over + // as we can't recalculate them later + st.CopyDependencies(f, t) + } else { + // Otherwise, we only copy over the type information + st.ExprTypes[t] = st.ExprTypes[f] + } case *sqlparser.Union: t, ok := to.(*sqlparser.Union) if !ok { From b084361edecc8f8faabca21e48dd7c915c10df44 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Thu, 11 Dec 2025 10:37:50 +0000 Subject: [PATCH 2/6] Update test case to also work in e2e tests. Signed-off-by: Arthur Schreiber --- go/vt/vtgate/planbuilder/testdata/select_cases.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases.json b/go/vt/vtgate/planbuilder/testdata/select_cases.json index fad7b90f634..e6ff1d78754 100644 --- a/go/vt/vtgate/planbuilder/testdata/select_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/select_cases.json @@ -4323,11 +4323,11 @@ }, { "comment": "Subquery with `IN` condition using columns with matching lookup vindexes, impossible conditions and limit clause", - "query": "SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id IN (1, 2, 3) AND 1 = 0 AND music.foo = 'bar' LIMIT 0, 100) _inner)", + "query": "SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id IN (1, 2, 3) AND 1 = 0 AND music.col1 = 'bar' LIMIT 0, 100) _inner)", "plan": { "Type": "Complex", "QueryType": "SELECT", - "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id IN (1, 2, 3) AND 1 = 0 AND music.foo = 'bar' LIMIT 0, 100) _inner)", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id IN (1, 2, 3) AND 1 = 0 AND music.col1 = 'bar' LIMIT 0, 100) _inner)", "Instructions": { "OperatorType": "UncorrelatedSubquery", "Variant": "PulloutIn", From a7ffd2c3bb72dc1a6794e04cee62cc12cf50a0ce Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Thu, 11 Dec 2025 11:07:08 +0000 Subject: [PATCH 3/6] Fix indentation. Signed-off-by: Arthur Schreiber --- go/vt/vtgate/planbuilder/testdata/select_cases.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases.json b/go/vt/vtgate/planbuilder/testdata/select_cases.json index e6ff1d78754..11d15afd8bc 100644 --- a/go/vt/vtgate/planbuilder/testdata/select_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/select_cases.json @@ -4295,7 +4295,7 @@ ] } }, - { + { "comment": "Subquery with `IN` condition using columns with matching lookup vindexes, with inner scatter query", "query": "SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.foo = 'bar') AND music.user_id IN (3, 4, 5)", "plan": { From ccacb2c42231b768d99f4de32c77d5aae95f3005 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Thu, 11 Dec 2025 14:15:19 +0000 Subject: [PATCH 4/6] Use `CopyExprInfo` to copy semantic table information. This fixes a bug where copying from an expression that had no type information would cause the target of the copy to have invalid information (as we'd be writing the null value which would prevent proper recalculation). Also added a bunch of unit test cases to cover `CopyExprInfo`. Signed-off-by: Arthur Schreiber --- go/vt/vtgate/semantics/semantic_table.go | 2 +- go/vt/vtgate/semantics/semantic_table_test.go | 126 ++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/go/vt/vtgate/semantics/semantic_table.go b/go/vt/vtgate/semantics/semantic_table.go index 94fd9ffd0cf..e9c126f9db0 100644 --- a/go/vt/vtgate/semantics/semantic_table.go +++ b/go/vt/vtgate/semantics/semantic_table.go @@ -557,7 +557,7 @@ func (st *SemTable) CopySemanticInfo(from, to sqlparser.SQLNode) { st.CopyDependencies(f, t) } else { // Otherwise, we only copy over the type information - st.ExprTypes[t] = st.ExprTypes[f] + st.CopyExprInfo(f, t) } case *sqlparser.Union: t, ok := to.(*sqlparser.Union) diff --git a/go/vt/vtgate/semantics/semantic_table_test.go b/go/vt/vtgate/semantics/semantic_table_test.go index 1f324215326..5bddee135d4 100644 --- a/go/vt/vtgate/semantics/semantic_table_test.go +++ b/go/vt/vtgate/semantics/semantic_table_test.go @@ -23,9 +23,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "vitess.io/vitess/go/mysql/collations" + "vitess.io/vitess/go/sqltypes" querypb "vitess.io/vitess/go/vt/proto/query" vschemapb "vitess.io/vitess/go/vt/proto/vschema" "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/vtgate/evalengine" "vitess.io/vitess/go/vt/vtgate/vindexes" ) @@ -979,3 +982,126 @@ func TestHasNonLiteralForeignKeyUpdate(t *testing.T) { }) } } + +func TestCopySemanticInfoNonColName(t *testing.T) { + t.Run("copies no semantic information when the source has no semantic information", func(t *testing.T) { + semTable := EmptySemTable() + + tableSet := SingleTableSet(0) + + col := &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("id")} + semTable.Recursive[col] = tableSet + semTable.Direct[col] = tableSet + semTable.ExprTypes[col] = evalengine.NewType(sqltypes.VarChar, collations.CollationUtf8mb4ID) + + from := &sqlparser.FuncExpr{ + Name: sqlparser.NewIdentifierCI("lower"), + Exprs: []sqlparser.Expr{col}, + } + + to := &sqlparser.FuncExpr{ + Name: sqlparser.NewIdentifierCI("upper"), + Exprs: []sqlparser.Expr{col}, + } + + semTable.CopySemanticInfo(from, to) + + require.NotContains(t, semTable.ExprTypes, to) + require.NotContains(t, semTable.Recursive, to) + require.NotContains(t, semTable.Direct, to) + }) + + t.Run("copies only the expression type when the source has semantic information", func(t *testing.T) { + semTable := EmptySemTable() + + tableSet := SingleTableSet(0) + + col := &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("id")} + semTable.Recursive[col] = tableSet + semTable.Direct[col] = tableSet + semTable.ExprTypes[col] = evalengine.NewType(sqltypes.VarChar, collations.CollationUtf8mb4ID) + + from := &sqlparser.FuncExpr{ + Name: sqlparser.NewIdentifierCI("lower"), + Exprs: []sqlparser.Expr{col}, + } + semTable.Recursive[from] = tableSet + semTable.Direct[from] = tableSet + semTable.ExprTypes[from] = evalengine.NewType(sqltypes.VarChar, collations.CollationUtf8mb4ID) + + to := &sqlparser.FuncExpr{ + Name: sqlparser.NewIdentifierCI("upper"), + Exprs: []sqlparser.Expr{col}, + } + + semTable.CopySemanticInfo(from, to) + + require.Contains(t, semTable.ExprTypes, to) + require.Equal(t, semTable.ExprTypes[from], semTable.ExprTypes[to]) + require.NotContains(t, semTable.Recursive, to) + require.NotContains(t, semTable.Direct, to) + }) +} + +func TestCopySemanticInfoIntoColName(t *testing.T) { + t.Run("copies all semantic information when the source has semantic information", func(t *testing.T) { + semTable := EmptySemTable() + + tableSet := SingleTableSet(0) + + col := &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("id")} + semTable.Recursive[col] = tableSet + semTable.Direct[col] = tableSet + semTable.ExprTypes[col] = evalengine.NewType(sqltypes.VarChar, collations.CollationUtf8mb4ID) + + from := &sqlparser.FuncExpr{ + Name: sqlparser.NewIdentifierCI("lower"), + Exprs: []sqlparser.Expr{col}, + } + semTable.Recursive[from] = tableSet + semTable.Direct[from] = tableSet + semTable.ExprTypes[from] = evalengine.NewType(sqltypes.VarChar, collations.CollationUtf8mb4ID) + + to := &sqlparser.ColName{ + Name: sqlparser.NewIdentifierCI("id"), + Qualifier: sqlparser.TableName{ + Name: sqlparser.NewIdentifierCS("derived"), + }, + } + + semTable.CopySemanticInfo(from, to) + + require.Contains(t, semTable.ExprTypes, to) + require.Contains(t, semTable.Recursive, to) + require.Contains(t, semTable.Direct, to) + }) + + t.Run("does not copy semantic information when the source has no semantic information", func(t *testing.T) { + semTable := EmptySemTable() + + tableSet := SingleTableSet(0) + + col := &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("id")} + semTable.Recursive[col] = tableSet + semTable.Direct[col] = tableSet + semTable.ExprTypes[col] = evalengine.NewType(sqltypes.VarChar, collations.CollationUtf8mb4ID) + + from := &sqlparser.FuncExpr{ + Name: sqlparser.NewIdentifierCI("lower"), + Exprs: []sqlparser.Expr{col}, + } + + to := &sqlparser.ColName{ + Name: sqlparser.NewIdentifierCI("id"), + Qualifier: sqlparser.TableName{ + Name: sqlparser.NewIdentifierCS("derived"), + }, + } + + semTable.CopySemanticInfo(from, to) + + require.NotContains(t, semTable.ExprTypes, to) + require.NotContains(t, semTable.Recursive, to) + require.NotContains(t, semTable.Direct, to) + }) +} From 1f6e95d9b798e2ec81307e0ad01dbd24857347cc Mon Sep 17 00:00:00 2001 From: Andy Edison Date: Tue, 6 Jan 2026 21:01:38 +0000 Subject: [PATCH 5/6] Remove function from earlier versions that got backported --- .../planbuilder/operators/subquery_builder.go | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/go/vt/vtgate/planbuilder/operators/subquery_builder.go b/go/vt/vtgate/planbuilder/operators/subquery_builder.go index 7c0de3d9823..cd2abb66a48 100644 --- a/go/vt/vtgate/planbuilder/operators/subquery_builder.go +++ b/go/vt/vtgate/planbuilder/operators/subquery_builder.go @@ -190,46 +190,6 @@ func createSubquery( } } -func createSubqueryFromPath( - ctx *plancontext.PlanningContext, - original sqlparser.Expr, - subq *sqlparser.Subquery, - path sqlparser.ASTPath, - outerID semantics.TableSet, - parent sqlparser.Expr, - argName string, - filterType opcode.PulloutOpcode, - isArg bool, -) *SubQuery { - topLevel := ctx.SemTable.EqualsExpr(original, parent) - original = cloneASTAndSemState(ctx, original) - originalSq := sqlparser.GetNodeFromPath(original, path).(*sqlparser.Subquery) - subqID := findTablesContained(ctx, originalSq.Select) - totalID := subqID.Merge(outerID) - sqc := &SubQueryBuilder{totalID: totalID, subqID: subqID, outerID: outerID} - - predicates, joinCols := sqc.inspectStatement(ctx, subq.Select) - - subqDependencies := ctx.SemTable.RecursiveDeps(subq) - correlated := subqDependencies.KeepOnly(outerID).NotEmpty() - - opInner := translateQueryToOp(ctx, subq.Select) - - opInner = sqc.getRootOperator(opInner, nil) - return &SubQuery{ - FilterType: filterType, - Subquery: opInner, - Predicates: predicates, - Original: original, - ArgName: argName, - originalSubquery: originalSq, - IsArgument: isArg, - TopLevel: topLevel, - JoinColumns: joinCols, - correlated: correlated, - } -} - func (sqb *SubQueryBuilder) inspectWhere( ctx *plancontext.PlanningContext, in *sqlparser.Where, From 63f633deaf6301b730cdc32cd6f260961a46d7bc Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Thu, 18 Dec 2025 14:41:47 +0000 Subject: [PATCH 6/6] clean up test cases Signed-off-by: Arthur Schreiber --- go/vt/vtgate/semantics/semantic_table_test.go | 93 +++++++++---------- 1 file changed, 44 insertions(+), 49 deletions(-) diff --git a/go/vt/vtgate/semantics/semantic_table_test.go b/go/vt/vtgate/semantics/semantic_table_test.go index 5bddee135d4..133d56e32f9 100644 --- a/go/vt/vtgate/semantics/semantic_table_test.go +++ b/go/vt/vtgate/semantics/semantic_table_test.go @@ -985,25 +985,24 @@ func TestHasNonLiteralForeignKeyUpdate(t *testing.T) { func TestCopySemanticInfoNonColName(t *testing.T) { t.Run("copies no semantic information when the source has no semantic information", func(t *testing.T) { - semTable := EmptySemTable() + parser := sqlparser.NewTestParser() + + col, err := parser.ParseExpr("id") + require.NoError(t, err) + + from, err := parser.ParseExpr("lower(id)") + require.NoError(t, err) + + to, err := parser.ParseExpr("upper(id)") + require.NoError(t, err) + semTable := EmptySemTable() tableSet := SingleTableSet(0) - col := &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("id")} semTable.Recursive[col] = tableSet semTable.Direct[col] = tableSet semTable.ExprTypes[col] = evalengine.NewType(sqltypes.VarChar, collations.CollationUtf8mb4ID) - from := &sqlparser.FuncExpr{ - Name: sqlparser.NewIdentifierCI("lower"), - Exprs: []sqlparser.Expr{col}, - } - - to := &sqlparser.FuncExpr{ - Name: sqlparser.NewIdentifierCI("upper"), - Exprs: []sqlparser.Expr{col}, - } - semTable.CopySemanticInfo(from, to) require.NotContains(t, semTable.ExprTypes, to) @@ -1012,28 +1011,28 @@ func TestCopySemanticInfoNonColName(t *testing.T) { }) t.Run("copies only the expression type when the source has semantic information", func(t *testing.T) { - semTable := EmptySemTable() + parser := sqlparser.NewTestParser() + + col, err := parser.ParseExpr("id") + require.NoError(t, err) + + from, err := parser.ParseExpr("lower(id)") + require.NoError(t, err) + + to, err := parser.ParseExpr("upper(id)") + require.NoError(t, err) + semTable := EmptySemTable() tableSet := SingleTableSet(0) - col := &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("id")} semTable.Recursive[col] = tableSet semTable.Direct[col] = tableSet semTable.ExprTypes[col] = evalengine.NewType(sqltypes.VarChar, collations.CollationUtf8mb4ID) - from := &sqlparser.FuncExpr{ - Name: sqlparser.NewIdentifierCI("lower"), - Exprs: []sqlparser.Expr{col}, - } semTable.Recursive[from] = tableSet semTable.Direct[from] = tableSet semTable.ExprTypes[from] = evalengine.NewType(sqltypes.VarChar, collations.CollationUtf8mb4ID) - to := &sqlparser.FuncExpr{ - Name: sqlparser.NewIdentifierCI("upper"), - Exprs: []sqlparser.Expr{col}, - } - semTable.CopySemanticInfo(from, to) require.Contains(t, semTable.ExprTypes, to) @@ -1045,30 +1044,28 @@ func TestCopySemanticInfoNonColName(t *testing.T) { func TestCopySemanticInfoIntoColName(t *testing.T) { t.Run("copies all semantic information when the source has semantic information", func(t *testing.T) { - semTable := EmptySemTable() + parser := sqlparser.NewTestParser() + col, err := parser.ParseExpr("id") + require.NoError(t, err) + + from, err := parser.ParseExpr("lower(id)") + require.NoError(t, err) + + to, err := parser.ParseExpr("derived.id") + require.NoError(t, err) + + semTable := EmptySemTable() tableSet := SingleTableSet(0) - col := &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("id")} semTable.Recursive[col] = tableSet semTable.Direct[col] = tableSet semTable.ExprTypes[col] = evalengine.NewType(sqltypes.VarChar, collations.CollationUtf8mb4ID) - from := &sqlparser.FuncExpr{ - Name: sqlparser.NewIdentifierCI("lower"), - Exprs: []sqlparser.Expr{col}, - } semTable.Recursive[from] = tableSet semTable.Direct[from] = tableSet semTable.ExprTypes[from] = evalengine.NewType(sqltypes.VarChar, collations.CollationUtf8mb4ID) - to := &sqlparser.ColName{ - Name: sqlparser.NewIdentifierCI("id"), - Qualifier: sqlparser.TableName{ - Name: sqlparser.NewIdentifierCS("derived"), - }, - } - semTable.CopySemanticInfo(from, to) require.Contains(t, semTable.ExprTypes, to) @@ -1077,27 +1074,25 @@ func TestCopySemanticInfoIntoColName(t *testing.T) { }) t.Run("does not copy semantic information when the source has no semantic information", func(t *testing.T) { + parser := sqlparser.NewTestParser() + + col, err := parser.ParseExpr("id") + require.NoError(t, err) + + from, err := parser.ParseExpr("lower(id)") + require.NoError(t, err) + + to, err := parser.ParseExpr("derived.id") + require.NoError(t, err) + semTable := EmptySemTable() tableSet := SingleTableSet(0) - col := &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("id")} semTable.Recursive[col] = tableSet semTable.Direct[col] = tableSet semTable.ExprTypes[col] = evalengine.NewType(sqltypes.VarChar, collations.CollationUtf8mb4ID) - from := &sqlparser.FuncExpr{ - Name: sqlparser.NewIdentifierCI("lower"), - Exprs: []sqlparser.Expr{col}, - } - - to := &sqlparser.ColName{ - Name: sqlparser.NewIdentifierCI("id"), - Qualifier: sqlparser.TableName{ - Name: sqlparser.NewIdentifierCS("derived"), - }, - } - semTable.CopySemanticInfo(from, to) require.NotContains(t, semTable.ExprTypes, to)