Skip to content

Commit f9535b2

Browse files
[release-18.0] planbuilder bugfix: expose columns through derived tables (#14501) (#14504)
Co-authored-by: Andrés Taylor <andres@planetscale.com>
1 parent e128bb9 commit f9535b2

File tree

9 files changed

+222
-28
lines changed

9 files changed

+222
-28
lines changed

go/test/endtoend/vtgate/queries/informationschema/informationschema_test.go

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,12 @@ import (
2121
"fmt"
2222
"testing"
2323

24-
"vitess.io/vitess/go/test/endtoend/utils"
25-
2624
"github.com/stretchr/testify/assert"
27-
2825
"github.com/stretchr/testify/require"
2926

3027
"vitess.io/vitess/go/mysql"
3128
"vitess.io/vitess/go/test/endtoend/cluster"
29+
"vitess.io/vitess/go/test/endtoend/utils"
3230
)
3331

3432
func start(t *testing.T) (utils.MySQLCompare, func()) {
@@ -205,23 +203,31 @@ func TestMultipleSchemaPredicates(t *testing.T) {
205203
require.Contains(t, err.Error(), "specifying two different database in the query is not supported")
206204
}
207205

208-
func TestInfrSchemaAndUnionAll(t *testing.T) {
209-
clusterInstance.VtGateExtraArgs = append(clusterInstance.VtGateExtraArgs, "--planner-version=gen4")
210-
require.NoError(t,
211-
clusterInstance.RestartVtgate())
212-
213-
vtConnParams := clusterInstance.GetVTParams(keyspaceName)
214-
vtConnParams.DbName = keyspaceName
215-
conn, err := mysql.Connect(context.Background(), &vtConnParams)
216-
require.NoError(t, err)
206+
func TestJoinWithSingleShardQueryOnRHS(t *testing.T) {
207+
// This test checks that we can run queries like this, where the RHS is a single shard query
208+
mcmp, closer := start(t)
209+
defer closer()
217210

218-
for _, workload := range []string{"oltp", "olap"} {
219-
t.Run(workload, func(t *testing.T) {
220-
utils.Exec(t, conn, fmt.Sprintf("set workload = %s", workload))
221-
utils.Exec(t, conn, "start transaction")
222-
utils.Exec(t, conn, `select connection_id()`)
223-
utils.Exec(t, conn, `(select 'corder' from t1 limit 1) union all (select 'customer' from t7_xxhash limit 1)`)
224-
utils.Exec(t, conn, "rollback")
225-
})
226-
}
211+
query := `SELECT
212+
c.column_name as column_name,
213+
c.data_type as data_type,
214+
c.table_name as table_name,
215+
c.table_schema as table_schema
216+
FROM
217+
information_schema.columns c
218+
JOIN (
219+
SELECT
220+
table_name
221+
FROM
222+
information_schema.tables
223+
WHERE
224+
table_schema != 'information_schema'
225+
LIMIT
226+
1
227+
) AS tables ON tables.table_name = c.table_name
228+
ORDER BY
229+
c.table_name`
230+
231+
res := utils.Exec(t, mcmp.VtConn, query)
232+
require.NotEmpty(t, res.Rows)
227233
}

go/vt/vtgate/planbuilder/operators/aggregator.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,13 @@ func (a *Aggregator) isDerived() bool {
124124
return a.DT != nil
125125
}
126126

127-
func (a *Aggregator) FindCol(ctx *plancontext.PlanningContext, in sqlparser.Expr, _ bool) (int, error) {
127+
func (a *Aggregator) FindCol(ctx *plancontext.PlanningContext, in sqlparser.Expr, underRoute bool) (int, error) {
128+
if underRoute && a.isDerived() {
129+
// We don't want to use columns on this operator if it's a derived table under a route.
130+
// In this case, we need to add a Projection on top of this operator to make the column available
131+
return -1, nil
132+
}
133+
128134
expr, err := a.DT.RewriteExpression(ctx, in)
129135
if err != nil {
130136
return 0, err

go/vt/vtgate/planbuilder/operators/horizon.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,12 @@ func canReuseColumn[T any](
151151
return
152152
}
153153

154-
func (h *Horizon) FindCol(ctx *plancontext.PlanningContext, expr sqlparser.Expr, _ bool) (int, error) {
154+
func (h *Horizon) FindCol(ctx *plancontext.PlanningContext, expr sqlparser.Expr, underRoute bool) (int, error) {
155+
if underRoute && h.IsDerived() {
156+
// We don't want to use columns on this operator if it's a derived table under a route.
157+
// In this case, we need to add a Projection on top of this operator to make the column available
158+
return -1, nil
159+
}
155160
for idx, se := range sqlparser.GetFirstSelect(h.Query).SelectExprs {
156161
ae, ok := se.(*sqlparser.AliasedExpr)
157162
if !ok {

go/vt/vtgate/planbuilder/operators/route_planning.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,19 +375,19 @@ func mergeOrJoin(ctx *plancontext.PlanningContext, lhs, rhs ops.Operator, joinPr
375375

376376
if len(joinPredicates) > 0 && requiresSwitchingSides(ctx, rhs) {
377377
if !inner {
378-
return nil, nil, vterrors.VT12001("LEFT JOIN with derived tables")
378+
return nil, nil, vterrors.VT12001("LEFT JOIN with LIMIT on the outer side")
379379
}
380380

381381
if requiresSwitchingSides(ctx, lhs) {
382-
return nil, nil, vterrors.VT12001("JOIN between derived tables")
382+
return nil, nil, vterrors.VT12001("JOIN between derived tables with LIMIT")
383383
}
384384

385385
join := NewApplyJoin(Clone(rhs), Clone(lhs), nil, !inner)
386386
newOp, err := pushJoinPredicates(ctx, joinPredicates, join)
387387
if err != nil {
388388
return nil, nil, err
389389
}
390-
return newOp, rewrite.NewTree("logical join to applyJoin, switching side because derived table", newOp), nil
390+
return newOp, rewrite.NewTree("logical join to applyJoin, switching side because LIMIT", newOp), nil
391391
}
392392

393393
join := NewApplyJoin(Clone(lhs), Clone(rhs), nil, !inner)

go/vt/vtgate/planbuilder/testdata/aggr_cases.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5956,5 +5956,53 @@
59565956
"user.user"
59575957
]
59585958
}
5959+
},
5960+
{
5961+
"comment": "GROUP BY inside derived table on the RHS should not be a problem",
5962+
"query": "SELECT c.column_name FROM user c JOIN (SELECT table_name FROM user WHERE id = 143 GROUP BY 1) AS tables ON tables.table_name = c.table_name",
5963+
"plan": {
5964+
"QueryType": "SELECT",
5965+
"Original": "SELECT c.column_name FROM user c JOIN (SELECT table_name FROM user WHERE id = 143 GROUP BY 1) AS tables ON tables.table_name = c.table_name",
5966+
"Instructions": {
5967+
"OperatorType": "Join",
5968+
"Variant": "Join",
5969+
"JoinColumnIndexes": "R:0",
5970+
"JoinVars": {
5971+
"tables_table_name": 0
5972+
},
5973+
"TableName": "`user`_`user`",
5974+
"Inputs": [
5975+
{
5976+
"OperatorType": "Route",
5977+
"Variant": "EqualUnique",
5978+
"Keyspace": {
5979+
"Name": "user",
5980+
"Sharded": true
5981+
},
5982+
"FieldQuery": "select table_name from (select table_name from `user` where 1 != 1 group by table_name) as `tables` where 1 != 1",
5983+
"Query": "select table_name from (select table_name from `user` where id = 143 group by table_name) as `tables`",
5984+
"Table": "`user`",
5985+
"Values": [
5986+
"INT64(143)"
5987+
],
5988+
"Vindex": "user_index"
5989+
},
5990+
{
5991+
"OperatorType": "Route",
5992+
"Variant": "Scatter",
5993+
"Keyspace": {
5994+
"Name": "user",
5995+
"Sharded": true
5996+
},
5997+
"FieldQuery": "select c.column_name from `user` as c where 1 != 1",
5998+
"Query": "select c.column_name from `user` as c where c.table_name = :tables_table_name",
5999+
"Table": "`user`"
6000+
}
6001+
]
6002+
},
6003+
"TablesUsed": [
6004+
"user.user"
6005+
]
6006+
}
59596007
}
59606008
]

go/vt/vtgate/planbuilder/testdata/info_schema57_cases.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,5 +1139,47 @@
11391139
"Table": "information_schema.apa"
11401140
}
11411141
}
1142+
},
1143+
{
1144+
"comment": "LIMIT 1 inside derived table on the RHS should not be a problem",
1145+
"query": "SELECT c.column_name FROM information_schema.columns c JOIN ( SELECT table_name FROM information_schema.tables WHERE table_schema != 'information_schema' LIMIT 1 ) AS tables ON tables.table_name = c.table_name",
1146+
"plan": {
1147+
"QueryType": "SELECT",
1148+
"Original": "SELECT c.column_name FROM information_schema.columns c JOIN ( SELECT table_name FROM information_schema.tables WHERE table_schema != 'information_schema' LIMIT 1 ) AS tables ON tables.table_name = c.table_name",
1149+
"Instructions": {
1150+
"OperatorType": "Join",
1151+
"Variant": "Join",
1152+
"JoinColumnIndexes": "R:0",
1153+
"JoinVars": {
1154+
"tables_table_name": 0
1155+
},
1156+
"TableName": "information_schema.`tables`_information_schema.`columns`",
1157+
"Inputs": [
1158+
{
1159+
"OperatorType": "Route",
1160+
"Variant": "DBA",
1161+
"Keyspace": {
1162+
"Name": "main",
1163+
"Sharded": false
1164+
},
1165+
"FieldQuery": "select table_name from (select table_name from information_schema.`tables` where 1 != 1) as `tables` where 1 != 1",
1166+
"Query": "select table_name from (select table_name from information_schema.`tables` where table_schema != 'information_schema' limit 1) as `tables`",
1167+
"Table": "information_schema.`tables`"
1168+
},
1169+
{
1170+
"OperatorType": "Route",
1171+
"Variant": "DBA",
1172+
"Keyspace": {
1173+
"Name": "main",
1174+
"Sharded": false
1175+
},
1176+
"FieldQuery": "select c.column_name from information_schema.`columns` as c where 1 != 1",
1177+
"Query": "select c.column_name from information_schema.`columns` as c where c.table_name = :c_table_name /* VARCHAR */",
1178+
"SysTableTableName": "[c_table_name::tables_table_name]",
1179+
"Table": "information_schema.`columns`"
1180+
}
1181+
]
1182+
}
1183+
}
11421184
}
11431185
]

go/vt/vtgate/planbuilder/testdata/info_schema80_cases.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,5 +1261,47 @@
12611261
"Table": "information_schema.apa"
12621262
}
12631263
}
1264+
},
1265+
{
1266+
"comment": "LIMIT 1 inside derived table on the RHS should not be a problem",
1267+
"query": "SELECT c.column_name FROM information_schema.columns c JOIN ( SELECT table_name FROM information_schema.tables WHERE table_schema != 'information_schema' LIMIT 1 ) AS tables ON tables.table_name = c.table_name",
1268+
"plan": {
1269+
"QueryType": "SELECT",
1270+
"Original": "SELECT c.column_name FROM information_schema.columns c JOIN ( SELECT table_name FROM information_schema.tables WHERE table_schema != 'information_schema' LIMIT 1 ) AS tables ON tables.table_name = c.table_name",
1271+
"Instructions": {
1272+
"OperatorType": "Join",
1273+
"Variant": "Join",
1274+
"JoinColumnIndexes": "R:0",
1275+
"JoinVars": {
1276+
"tables_table_name": 0
1277+
},
1278+
"TableName": "information_schema.`tables`_information_schema.`columns`",
1279+
"Inputs": [
1280+
{
1281+
"OperatorType": "Route",
1282+
"Variant": "DBA",
1283+
"Keyspace": {
1284+
"Name": "main",
1285+
"Sharded": false
1286+
},
1287+
"FieldQuery": "select table_name from (select table_name from information_schema.`tables` where 1 != 1) as `tables` where 1 != 1",
1288+
"Query": "select table_name from (select table_name from information_schema.`tables` where table_schema != 'information_schema' limit 1) as `tables`",
1289+
"Table": "information_schema.`tables`"
1290+
},
1291+
{
1292+
"OperatorType": "Route",
1293+
"Variant": "DBA",
1294+
"Keyspace": {
1295+
"Name": "main",
1296+
"Sharded": false
1297+
},
1298+
"FieldQuery": "select c.column_name from information_schema.`columns` as c where 1 != 1",
1299+
"Query": "select c.column_name from information_schema.`columns` as c where c.table_name = :c_table_name /* VARCHAR */",
1300+
"SysTableTableName": "[c_table_name::tables_table_name]",
1301+
"Table": "information_schema.`columns`"
1302+
}
1303+
]
1304+
}
1305+
}
12641306
}
12651307
]

go/vt/vtgate/planbuilder/testdata/select_cases.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4808,5 +4808,50 @@
48084808
"user.user_extra"
48094809
]
48104810
}
4811+
},
4812+
{
4813+
"comment": "Derived tables going to a single shard still need to expand derived table columns",
4814+
"query": "SELECT c.column_name FROM user c JOIN (SELECT table_name FROM unsharded LIMIT 1) AS tables ON tables.table_name = c.table_name",
4815+
"plan": {
4816+
"QueryType": "SELECT",
4817+
"Original": "SELECT c.column_name FROM user c JOIN (SELECT table_name FROM unsharded LIMIT 1) AS tables ON tables.table_name = c.table_name",
4818+
"Instructions": {
4819+
"OperatorType": "Join",
4820+
"Variant": "Join",
4821+
"JoinColumnIndexes": "R:0",
4822+
"JoinVars": {
4823+
"tables_table_name": 0
4824+
},
4825+
"TableName": "unsharded_`user`",
4826+
"Inputs": [
4827+
{
4828+
"OperatorType": "Route",
4829+
"Variant": "Unsharded",
4830+
"Keyspace": {
4831+
"Name": "main",
4832+
"Sharded": false
4833+
},
4834+
"FieldQuery": "select table_name from (select table_name from unsharded where 1 != 1) as `tables` where 1 != 1",
4835+
"Query": "select table_name from (select table_name from unsharded limit 1) as `tables`",
4836+
"Table": "unsharded"
4837+
},
4838+
{
4839+
"OperatorType": "Route",
4840+
"Variant": "Scatter",
4841+
"Keyspace": {
4842+
"Name": "user",
4843+
"Sharded": true
4844+
},
4845+
"FieldQuery": "select c.column_name from `user` as c where 1 != 1",
4846+
"Query": "select c.column_name from `user` as c where c.table_name = :tables_table_name",
4847+
"Table": "`user`"
4848+
}
4849+
]
4850+
},
4851+
"TablesUsed": [
4852+
"main.unsharded",
4853+
"user.user"
4854+
]
4855+
}
48114856
}
48124857
]

go/vt/vtgate/planbuilder/testdata/unsupported_cases.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,12 +482,12 @@
482482
{
483483
"comment": "cant switch sides for outer joins",
484484
"query": "select id from user left join (select user_id from user_extra limit 10) ue on user.id = ue.user_id",
485-
"plan": "VT12001: unsupported: LEFT JOIN with derived tables"
485+
"plan": "VT12001: unsupported: LEFT JOIN with LIMIT on the outer side"
486486
},
487487
{
488488
"comment": "limit on both sides means that we can't evaluate this at all",
489489
"query": "select id from (select id from user limit 10) u join (select user_id from user_extra limit 10) ue on u.id = ue.user_id",
490-
"plan": "VT12001: unsupported: JOIN between derived tables"
490+
"plan": "VT12001: unsupported: JOIN between derived tables with LIMIT"
491491
},
492492
{
493493
"comment": "multi-shard union",

0 commit comments

Comments
 (0)