Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix dual merging in outer join queries #15959

Merged
merged 6 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 46 additions & 9 deletions go/vt/vtgate/planbuilder/operators/join_merging.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,21 @@ import (
// mergeJoinInputs checks whether two operators can be merged into a single one.
// If they can be merged, a new operator with the merged routing is returned
// If they cannot be merged, nil is returned.
func mergeJoinInputs(ctx *plancontext.PlanningContext, lhs, rhs Operator, joinPredicates []sqlparser.Expr, m merger) *Route {
func mergeJoinInputs(ctx *plancontext.PlanningContext, lhs, rhs Operator, joinPredicates []sqlparser.Expr, m *joinMerger) *Route {
lhsRoute, rhsRoute, routingA, routingB, a, b, sameKeyspace := prepareInputRoutes(lhs, rhs)
if lhsRoute == nil {
return nil
}

switch {
// if either side is a dual query, we can always merge them together
case a == dual:
// If a dual is on the left side and it is a left join (all right joins are changed to left joins), then we can only merge if the right side is a single sharded routing.
if !m.joinType.IsInner() && !willBecomeSingleShardOnFilter(ctx, rhsRoute, joinPredicates) {
return nil
}
return m.merge(ctx, lhsRoute, rhsRoute, routingB)
case b == dual:
// If a dual is on the right side.
return m.merge(ctx, lhsRoute, rhsRoute, routingA)

// an unsharded/reference route can be merged with anything going to that keyspace
Expand Down Expand Up @@ -65,6 +69,45 @@ func mergeJoinInputs(ctx *plancontext.PlanningContext, lhs, rhs Operator, joinPr
}
}

// willBecomeSingleShardOnFilter returns whether the given route will become a single sharded route after pushing the join predicates.
func willBecomeSingleShardOnFilter(ctx *plancontext.PlanningContext, a *Route, joinPredicates []sqlparser.Expr) bool {
// If the routing is already single sharded, it will remain so even after we push more predicates.
if a.Routing.OpCode().IsSingleShard() {
return true
}

// Go over all the join predicates.
for _, predicate := range joinPredicates {
comparison, ok := predicate.(*sqlparser.ComparisonExpr)
if !ok {
continue
}
if comparison.Operator != sqlparser.EqualOp {
continue
}
left := comparison.Left
right := comparison.Right

lVindex := findColumnVindex(ctx, a, left)
if lVindex == nil {
left, right = right, left
lVindex = findColumnVindex(ctx, a, left)
}

if lVindex == nil || !lVindex.IsUnique() {
continue
}
// If the predicate is an equal comparison with the left side having a unique column vindex,
// and the right side is a literal, then we know
// pushing this predicate will make the route a single sharded route.
if sqlparser.IsLiteral(right) {
return true
}
}

return false
}

func prepareInputRoutes(lhs Operator, rhs Operator) (*Route, *Route, Routing, Routing, routingType, routingType, bool) {
lhsRoute, rhsRoute := operatorsToRoutes(lhs, rhs)
if lhsRoute == nil || rhsRoute == nil {
Expand All @@ -74,12 +117,6 @@ func prepareInputRoutes(lhs Operator, rhs Operator) (*Route, *Route, Routing, Ro
lhsRoute, rhsRoute, routingA, routingB, sameKeyspace := getRoutesOrAlternates(lhsRoute, rhsRoute)

a, b := getRoutingType(routingA), getRoutingType(routingB)
if getTypeName(routingA) < getTypeName(routingB) {
// while deciding if two routes can be merged, the LHS/RHS order of the routes is not important.
// for the actual merging, we still need to remember which side was inner and which was outer for subqueries
a, b = b, a
routingA, routingB = routingB, routingA
}

return lhsRoute, rhsRoute, routingA, routingB, a, b, sameKeyspace
}
Expand Down Expand Up @@ -178,7 +215,7 @@ func getRoutingType(r Routing) routingType {
panic(fmt.Sprintf("switch should be exhaustive, got %T", r))
}

func newJoinMerge(predicates []sqlparser.Expr, joinType sqlparser.JoinType) merger {
func newJoinMerge(predicates []sqlparser.Expr, joinType sqlparser.JoinType) *joinMerger {
return &joinMerger{
predicates: predicates,
joinType: joinType,
Expand Down
153 changes: 153 additions & 0 deletions go/vt/vtgate/planbuilder/testdata/select_cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -2043,6 +2043,159 @@
]
}
},
{
GuptaManan100 marked this conversation as resolved.
Show resolved Hide resolved
"comment": "left join with a dual table on left - merge-able",
"query": "select t.title, user.col from (select 'hello' as title) as t left join user on user.id=1",
"plan": {
"QueryType": "SELECT",
"Original": "select t.title, user.col from (select 'hello' as title) as t left join user on user.id=1",
"Instructions": {
"OperatorType": "Route",
"Variant": "Scatter",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select t.title, `user`.col from (select 'hello' as title from dual where 1 != 1) as t left join `user` on `user`.id = 1 where 1 != 1",
"Query": "select t.title, `user`.col from (select 'hello' as title from dual) as t left join `user` on `user`.id = 1",
"Table": "`user`, dual"
},
"TablesUsed": [
"main.dual",
"user.user"
GuptaManan100 marked this conversation as resolved.
Show resolved Hide resolved
]
}
},
{
"comment": "left join with a dual table on left - non-merge-able",
"query": "select t.title, user.col from (select 'hello' as title) as t left join user on user.id <= 4",
"plan": {
"QueryType": "SELECT",
"Original": "select t.title, user.col from (select 'hello' as title) as t left join user on user.id <= 4",
"Instructions": {
"OperatorType": "Join",
"Variant": "LeftJoin",
"JoinColumnIndexes": "L:0,R:0",
"TableName": "dual_`user`",
"Inputs": [
{
"OperatorType": "Route",
"Variant": "Reference",
"Keyspace": {
"Name": "main",
"Sharded": false
},
"FieldQuery": "select t.title from (select 'hello' as title from dual where 1 != 1) as t where 1 != 1",
"Query": "select t.title from (select 'hello' as title from dual) as t",
"Table": "dual"
},
{
"OperatorType": "Route",
"Variant": "Scatter",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select `user`.col from `user` where 1 != 1",
"Query": "select `user`.col from `user` where `user`.id <= 4",
"Table": "`user`"
}
]
},
"TablesUsed": [
"main.dual",
"user.user"
]
}
},
{
"comment": "right join with a dual table on left",
"query": "select t.title, user.col from (select 'hello' as title) as t right join user on user.id<=4",
"plan": {
"QueryType": "SELECT",
"Original": "select t.title, user.col from (select 'hello' as title) as t right join user on user.id<=4",
"Instructions": {
"OperatorType": "Route",
"Variant": "Scatter",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select t.title, `user`.col from `user` left join (select 'hello' as title from dual where 1 != 1) as t on `user`.id <= 4 where 1 != 1",
"Query": "select t.title, `user`.col from `user` left join (select 'hello' as title from dual) as t on `user`.id <= 4",
"Table": "`user`, dual"
},
"TablesUsed": [
"main.dual",
"user.user"
]
}
},
{
"comment": "right join with a dual table on right - merge-able",
"query": "select t.title, user.col from user right join (select 'hello' as title) as t on user.id=1",
"plan": {
"QueryType": "SELECT",
"Original": "select t.title, user.col from user right join (select 'hello' as title) as t on user.id=1",
"Instructions": {
"OperatorType": "Route",
"Variant": "Scatter",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select t.title, `user`.col from (select 'hello' as title from dual where 1 != 1) as t left join `user` on `user`.id = 1 where 1 != 1",
"Query": "select t.title, `user`.col from (select 'hello' as title from dual) as t left join `user` on `user`.id = 1",
"Table": "`user`, dual"
},
"TablesUsed": [
"main.dual",
"user.user"
]
}
},
{
"comment": "right join with a dual table on right - non-merge-able",
"query": "select t.title, user.col from user right join (select 'hello' as title) as t on user.id>=4",
"plan": {
"QueryType": "SELECT",
"Original": "select t.title, user.col from user right join (select 'hello' as title) as t on user.id>=4",
"Instructions": {
"OperatorType": "Join",
"Variant": "LeftJoin",
"JoinColumnIndexes": "L:0,R:0",
"TableName": "dual_`user`",
"Inputs": [
{
"OperatorType": "Route",
"Variant": "Reference",
"Keyspace": {
"Name": "main",
"Sharded": false
},
"FieldQuery": "select t.title from (select 'hello' as title from dual where 1 != 1) as t where 1 != 1",
"Query": "select t.title from (select 'hello' as title from dual) as t",
"Table": "dual"
},
{
"OperatorType": "Route",
"Variant": "Scatter",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select `user`.col from `user` where 1 != 1",
"Query": "select `user`.col from `user` where `user`.id >= 4",
"Table": "`user`"
}
]
},
"TablesUsed": [
"main.dual",
"user.user"
]
}
},
{
"comment": "Union after into outfile is incorrect",
"query": "select id from user into outfile 'out_file_name' union all select id from music",
Expand Down
Loading