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

bugfix: Allow cross-keyspace joins #16520

Merged
merged 2 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions .github/workflows/vitess_tester_vtgate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ jobs:
go install github.com/vitessio/go-junit-report@HEAD

# install vitess tester
go install github.com/vitessio/vitess-tester@eb953122baba163ed8ccaa6642458ee984f5d7e4
go install github.com/vitessio/vitess-tester@89dd933a9ea0e15f69ca58b9c8ea09a358762cca

- name: Setup launchable dependencies
if: steps.skip-workflow.outputs.is_draft == 'false' && steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' && github.base_ref == 'main'
Expand Down Expand Up @@ -144,9 +144,9 @@ jobs:
# We go over all the directories in the given path.
# If there is a vschema file there, we use it, otherwise we let vitess-tester autogenerate it.
if [ -f $dir/vschema.json ]; then
vitess-tester --sharded --xunit --test-dir $dir --vschema "$dir"vschema.json
vitess-tester --xunit --vschema "$dir"vschema.json $dir/*.test
else
vitess-tester --sharded --xunit --test-dir $dir
vitess-tester --sharded --xunit $dir/*.test
fi
# Number the reports by changing their file names.
mv report.xml report"$i".xml
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use customer;
create table if not exists customer(
customer_id bigint not null,
email varbinary(128),
primary key(customer_id)
) ENGINE=InnoDB;
insert into customer.customer(customer_id, email) values(1, '[[email protected]](mailto:[email protected])');
insert into customer.customer(customer_id, email) values(2, '[[email protected]](mailto:[email protected])');
insert into customer.customer(customer_id, email) values(3, '[[email protected]](mailto:[email protected])');
insert into customer.customer(customer_id, email) values(4, '[[email protected]](mailto:[email protected])');
insert into customer.customer(customer_id, email) values(5, '[[email protected]](mailto:[email protected])');
use corder;
create table if not exists corder(
order_id bigint not null,
customer_id bigint,
sku varbinary(128),
price bigint,
primary key(order_id)
) ENGINE=InnoDB;
insert into corder.corder(order_id, customer_id, sku, price) values(1, 1, 'SKU-1001', 100);
insert into corder.corder(order_id, customer_id, sku, price) values(2, 2, 'SKU-1002', 30);
insert into corder.corder(order_id, customer_id, sku, price) values(3, 3, 'SKU-1002', 30);
insert into corder.corder(order_id, customer_id, sku, price) values(4, 4, 'SKU-1002', 30);
insert into corder.corder(order_id, customer_id, sku, price) values(5, 5, 'SKU-1002', 30);

select co.order_id, co.customer_id, co.price from corder.corder co left join customer.customer cu on co.customer_id=cu.customer_id where cu.customer_id=1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"keyspaces": {
"customer": {
"sharded": true,
"vindexes": {
"hash": {
"type": "hash",
"params": {},
"owner": ""
}
},
"tables": {
"customer": {
"type": "",
"column_vindexes": [
{
"column": "customer_id",
"name": "hash",
"columns": []
}
],
"columns": [],
"pinned": "",
"column_list_authoritative": false,
"source": ""
}
},
"require_explicit_routing": false,
"foreign_key_mode": 0,
"multi_tenant_spec": null
},
"corder": {
"sharded": true,
"vindexes": {
"hash": {
"type": "hash",
"params": {},
"owner": ""
}
},
"tables": {
"corder": {
"type": "",
"column_vindexes": [
{
"column": "customer_id",
"name": "hash",
"columns": []
}
],
"columns": [],
"pinned": "",
"column_list_authoritative": false,
"source": ""
}
},
"require_explicit_routing": false,
"foreign_key_mode": 0,
"multi_tenant_spec": null
}
},
"routing_rules": {
"rules": []
},
"shard_routing_rules": {
"rules": []
},
"keyspace_routing_rules": null,
"mirror_rules": {
"rules": []
}
}
2 changes: 1 addition & 1 deletion go/vt/vtgate/planbuilder/operators/join_merging.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func mergeJoinInputs(ctx *plancontext.PlanningContext, lhs, rhs Operator, joinPr

// sharded routing is complex, so we handle it in a separate method
case a == sharded && b == sharded:
return tryMergeJoinShardedRouting(ctx, lhsRoute, rhsRoute, m, joinPredicates)
return tryMergeShardedRouting(ctx, lhsRoute, rhsRoute, m, joinPredicates, false /*isSubquery*/)

default:
return nil
Expand Down
8 changes: 6 additions & 2 deletions go/vt/vtgate/planbuilder/operators/sharded_routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -634,11 +634,12 @@ func (tr *ShardedRouting) extraInfo() string {
)
}

func tryMergeJoinShardedRouting(
func tryMergeShardedRouting(
ctx *plancontext.PlanningContext,
routeA, routeB *Route,
m merger,
joinPredicates []sqlparser.Expr,
isSubquery bool,
) *Route {
sameKeyspace := routeA.Routing.Keyspace() == routeB.Routing.Keyspace()
tblA := routeA.Routing.(*ShardedRouting)
Expand Down Expand Up @@ -670,7 +671,10 @@ func tryMergeJoinShardedRouting(
}

if !sameKeyspace {
panic(vterrors.VT12001("cross-shard correlated subquery"))
if isSubquery {
panic(vterrors.VT12001("cross-shard correlated subquery"))
}
return nil
}

canMerge := canMergeOnFilters(ctx, routeA, routeB, joinPredicates)
Expand Down
2 changes: 1 addition & 1 deletion go/vt/vtgate/planbuilder/operators/subquery_planning.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,7 @@ func mergeSubqueryInputs(ctx *plancontext.PlanningContext, in, out Operator, joi

// sharded routing is complex, so we handle it in a separate method
case inner == sharded && outer == sharded:
return tryMergeJoinShardedRouting(ctx, inRoute, outRoute, m, joinPredicates)
return tryMergeShardedRouting(ctx, inRoute, outRoute, m, joinPredicates, true /*isSubquery*/)

default:
return nil
Expand Down
49 changes: 49 additions & 0 deletions go/vt/vtgate/planbuilder/testdata/from_cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -4619,6 +4619,55 @@
]
}
},
{
"comment": "Cross keyspace join",
"query": "select 1 from user join t1 on user.id = t1.id",
"plan": {
"QueryType": "SELECT",
"Original": "select 1 from user join t1 on user.id = t1.id",
"Instructions": {
"OperatorType": "Join",
"Variant": "Join",
"JoinColumnIndexes": "L:0",
"JoinVars": {
"t1_id": 1
},
"TableName": "t1_`user`",
"Inputs": [
{
"OperatorType": "Route",
"Variant": "Scatter",
"Keyspace": {
"Name": "zlookup_unique",
"Sharded": true
},
"FieldQuery": "select 1, t1.id from t1 where 1 != 1",
"Query": "select 1, t1.id from t1",
"Table": "t1"
},
{
"OperatorType": "Route",
"Variant": "EqualUnique",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select 1 from `user` where 1 != 1",
"Query": "select 1 from `user` where `user`.id = :t1_id",
"Table": "`user`",
"Values": [
":t1_id"
],
"Vindex": "user_index"
}
]
},
"TablesUsed": [
"user.user",
"zlookup_unique.t1"
]
}
},
{
"comment": "Select everything from a derived table having a cross-shard join",
"query": "select * from (select u.foo * ue.bar from user u join user_extra ue) as dt",
Expand Down
5 changes: 5 additions & 0 deletions go/vt/vtgate/planbuilder/testdata/unsupported_cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,11 @@
"query": "select 1 from music union (select id from user union all select name from unsharded)",
"plan": "VT12001: unsupported: nesting of UNIONs on the right-hand side"
},
{
"comment": "Cross keyspace query with subquery",
"query": "select 1 from user where id in (select id from t1)",
"plan": "VT12001: unsupported: cross-shard correlated subquery"
},
{
"comment": "multi-shard union",
"query": "select 1 from music union (select id from user union select name from unsharded)",
Expand Down
6 changes: 3 additions & 3 deletions test/templates/cluster_vitess_tester.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ jobs:
go install github.com/vitessio/go-junit-report@HEAD

# install vitess tester
go install github.com/vitessio/vitess-tester@eb953122baba163ed8ccaa6642458ee984f5d7e4
go install github.com/vitessio/vitess-tester@89dd933a9ea0e15f69ca58b9c8ea09a358762cca

- name: Setup launchable dependencies
if: steps.skip-workflow.outputs.is_draft == 'false' && steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' && github.base_ref == 'main'
Expand Down Expand Up @@ -142,9 +142,9 @@ jobs:
# We go over all the directories in the given path.
# If there is a vschema file there, we use it, otherwise we let vitess-tester autogenerate it.
if [ -f $dir/vschema.json ]; then
vitess-tester --sharded --xunit --test-dir $dir --vschema "$dir"vschema.json
vitess-tester --xunit --vschema "$dir"vschema.json $dir/*.test
else
vitess-tester --sharded --xunit --test-dir $dir
vitess-tester --sharded --xunit $dir/*.test
fi
# Number the reports by changing their file names.
mv report.xml report"$i".xml
Expand Down
Loading