diff --git a/go/test/endtoend/vtgate/mysql80/derived/derived_test.go b/go/test/endtoend/vtgate/mysql80/derived/derived_test.go deleted file mode 100644 index 47d5c4a7dcd..00000000000 --- a/go/test/endtoend/vtgate/mysql80/derived/derived_test.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2021 The Vitess Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package derived - -import ( - "context" - "testing" - - "vitess.io/vitess/go/test/endtoend/utils" - - "github.com/stretchr/testify/require" - - "vitess.io/vitess/go/mysql" - "vitess.io/vitess/go/test/endtoend/cluster" -) - -func TestDerivedTableColumns(t *testing.T) { - defer cluster.PanicHandler(t) - ctx := context.Background() - conn, err := mysql.Connect(ctx, &vtParams) - require.NoError(t, err) - defer conn.Close() - - utils.Exec(t, conn, `delete from t1`) - defer utils.Exec(t, conn, `delete from t1`) - - utils.Exec(t, conn, "insert into t1(id1, id2) values (0,10),(1,9),(2,8),(3,7),(4,6),(5,5)") - utils.AssertMatches(t, conn, `SELECT /*vt+ PLANNER=gen4 */ t.id FROM (SELECT id2 FROM t1) AS t(id) ORDER BY t.id DESC`, `[[INT64(10)] [INT64(9)] [INT64(8)] [INT64(7)] [INT64(6)] [INT64(5)]]`) -} diff --git a/go/test/endtoend/vtgate/queries/derived/derived_test.go b/go/test/endtoend/vtgate/queries/derived/derived_test.go new file mode 100644 index 00000000000..5da8d8bac9b --- /dev/null +++ b/go/test/endtoend/vtgate/queries/derived/derived_test.go @@ -0,0 +1,99 @@ +/* +Copyright 2022 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package misc + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "vitess.io/vitess/go/test/endtoend/cluster" + "vitess.io/vitess/go/test/endtoend/utils" +) + +func start(t *testing.T) (utils.MySQLCompare, func()) { + mcmp, err := utils.NewMySQLCompare(t, vtParams, mysqlParams) + require.NoError(t, err) + + deleteAll := func() { + tables := []string{"music"} + for _, table := range tables { + _, _ = mcmp.ExecAndIgnore("delete from " + table) + } + } + + deleteAll() + + return mcmp, func() { + deleteAll() + mcmp.Close() + cluster.PanicHandler(t) + } +} + +func TestDerivedTableWithOrderByLimit(t *testing.T) { + mcmp, closer := start(t) + defer closer() + + mcmp.Exec("insert into music(id, user_id) values(1,1), (2,5), (3,1), (4,2), (5,3), (6,4), (7,5)") + mcmp.Exec("insert into user(id, name) values(1,'toto'), (2,'tata'), (3,'titi'), (4,'tete'), (5,'foo')") + + mcmp.Exec("select /*vt+ PLANNER=Gen4 */ music.id from music join (select id,name from user order by id limit 2) as d on music.user_id = d.id") +} + +func TestDerivedAggregationOnRHS(t *testing.T) { + mcmp, closer := start(t) + defer closer() + + mcmp.Exec("insert into music(id, user_id) values(1,1), (2,5), (3,1), (4,2), (5,3), (6,4), (7,5)") + mcmp.Exec("insert into user(id, name) values(1,'toto'), (2,'tata'), (3,'titi'), (4,'tete'), (5,'foo')") + + mcmp.Exec("set sql_mode = ''") + mcmp.Exec("select /*vt+ PLANNER=Gen4 */ d.a from music join (select id, count(*) as a from user) as d on music.user_id = d.id group by 1") +} + +func TestDerivedRemoveInnerOrderBy(t *testing.T) { + mcmp, closer := start(t) + defer closer() + + mcmp.Exec("insert into music(id, user_id) values(1,1), (2,5), (3,1), (4,2), (5,3), (6,4), (7,5)") + mcmp.Exec("insert into user(id, name) values(1,'toto'), (2,'tata'), (3,'titi'), (4,'tete'), (5,'foo')") + + mcmp.Exec("select /*vt+ PLANNER=Gen4 */ count(*) from (select user.id as oui, music.id as non from user join music on user.id = music.user_id order by user.name) as toto") +} + +func TestDerivedTableWithHaving(t *testing.T) { + mcmp, closer := start(t) + defer closer() + + mcmp.Exec("insert into music(id, user_id) values(1,1), (2,5), (3,1), (4,2), (5,3), (6,4), (7,5)") + mcmp.Exec("insert into user(id, name) values(1,'toto'), (2,'tata'), (3,'titi'), (4,'tete'), (5,'foo')") + + mcmp.Exec("set sql_mode = ''") + + // this is probably flaky? the id returned from the derived table could be any of the ids from user. + // works on my machine (TM) + mcmp.Exec("select /*vt+ PLANNER=Gen4 */ * from (select id from user having count(*) >= 1) s") +} + +func TestDerivedTableColumns(t *testing.T) { + mcmp, closer := start(t) + defer closer() + + mcmp.Exec("insert into user(id, name) values(1,'toto'), (2,'tata'), (3,'titi'), (4,'tete'), (5,'foo')") + mcmp.AssertMatches(`SELECT /*vt+ PLANNER=gen4 */ t.id FROM (SELECT id FROM user) AS t(id) ORDER BY t.id DESC`, `[[INT64(5)] [INT64(4)] [INT64(3)] [INT64(2)] [INT64(1)]]`) +} diff --git a/go/test/endtoend/vtgate/mysql80/derived/main_test.go b/go/test/endtoend/vtgate/queries/derived/main_test.go similarity index 53% rename from go/test/endtoend/vtgate/mysql80/derived/main_test.go rename to go/test/endtoend/vtgate/queries/derived/main_test.go index 2619d0d65fc..59a3c40e70c 100644 --- a/go/test/endtoend/vtgate/mysql80/derived/main_test.go +++ b/go/test/endtoend/vtgate/queries/derived/main_test.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Vitess Authors. +Copyright 2022 The Vitess Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,13 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package derived +package misc import ( + _ "embed" "flag" + "fmt" "os" "testing" + "vitess.io/vitess/go/test/endtoend/utils" + "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/test/endtoend/cluster" ) @@ -28,61 +32,15 @@ import ( var ( clusterInstance *cluster.LocalProcessCluster vtParams mysql.ConnParams - KeyspaceName = "ks_union" - Cell = "test_union" - SchemaSQL = `create table t1( - id1 bigint, - id2 bigint, - primary key(id1) -) Engine=InnoDB; - -create table t1_id2_idx( - id2 bigint, - keyspace_id varbinary(10), - primary key(id2) -) Engine=InnoDB; -` - - VSchema = ` -{ - "sharded": true, - "vindexes": { - "hash": { - "type": "hash" - }, - "t1_id2_vdx": { - "type": "consistent_lookup_unique", - "params": { - "table": "t1_id2_idx", - "from": "id2", - "to": "keyspace_id" - }, - "owner": "t1" - } - }, - "tables": { - "t1": { - "column_vindexes": [ - { - "column": "id1", - "name": "hash" - }, - { - "column": "id2", - "name": "t1_id2_vdx" - } - ] - }, - "t1_id2_idx": { - "column_vindexes": [ - { - "column": "id2", - "name": "hash" - } - ] - } - } -}` + mysqlParams mysql.ConnParams + keyspaceName = "ks_derived" + cell = "test_derived" + + //go:embed schema.sql + schemaSQL string + + //go:embed vschema.json + vschema string ) func TestMain(m *testing.M) { @@ -90,7 +48,7 @@ func TestMain(m *testing.M) { flag.Parse() exitCode := func() int { - clusterInstance = cluster.NewCluster(Cell, "localhost") + clusterInstance = cluster.NewCluster(cell, "localhost") defer clusterInstance.Teardown() // Start topo server @@ -101,12 +59,10 @@ func TestMain(m *testing.M) { // Start keyspace keyspace := &cluster.Keyspace{ - Name: KeyspaceName, - SchemaSQL: SchemaSQL, - VSchema: VSchema, + Name: keyspaceName, + SchemaSQL: schemaSQL, + VSchema: vschema, } - clusterInstance.VtGateExtraArgs = []string{"--schema_change_signal"} - clusterInstance.VtTabletExtraArgs = []string{"--queryserver-config-schema-change-signal", "--queryserver-config-schema-change-signal-interval", "0.1"} err = clusterInstance.StartKeyspace(*keyspace, []string{"-80", "80-"}, 1, true) if err != nil { return 1 @@ -123,6 +79,15 @@ func TestMain(m *testing.M) { Host: clusterInstance.Hostname, Port: clusterInstance.VtgateMySQLPort, } + + // create mysql instance and connection parameters + conn, closer, err := utils.NewMySQL(clusterInstance, keyspaceName, schemaSQL) + if err != nil { + fmt.Println(err) + return 1 + } + defer closer() + mysqlParams = conn return m.Run() }() os.Exit(exitCode) diff --git a/go/test/endtoend/vtgate/queries/derived/schema.sql b/go/test/endtoend/vtgate/queries/derived/schema.sql new file mode 100644 index 00000000000..cf608028ed5 --- /dev/null +++ b/go/test/endtoend/vtgate/queries/derived/schema.sql @@ -0,0 +1,13 @@ +create table user +( + id bigint, + name varchar(255), + primary key (id) +) Engine = InnoDB; + +create table music +( + id bigint, + user_id bigint, + primary key (id) +) Engine = InnoDB; diff --git a/go/test/endtoend/vtgate/queries/derived/vschema.json b/go/test/endtoend/vtgate/queries/derived/vschema.json new file mode 100644 index 00000000000..82461e6e57a --- /dev/null +++ b/go/test/endtoend/vtgate/queries/derived/vschema.json @@ -0,0 +1,26 @@ +{ + "sharded": true, + "vindexes": { + "user_index": { + "type": "hash" + } + }, + "tables": { + "user": { + "column_vindexes": [ + { + "column": "id", + "name": "user_index" + } + ] + }, + "music": { + "column_vindexes": [ + { + "column": "user_id", + "name": "user_index" + } + ] + } + } +} diff --git a/go/vt/sqlparser/ast.go b/go/vt/sqlparser/ast.go index 21aa94fe25e..c25ff685035 100644 --- a/go/vt/sqlparser/ast.go +++ b/go/vt/sqlparser/ast.go @@ -56,12 +56,14 @@ type ( AddOrder(*Order) SetOrderBy(OrderBy) GetOrderBy() OrderBy + GetLimit() *Limit SetLimit(*Limit) SetLock(lock Lock) SetInto(into *SelectInto) SetWith(with *With) MakeDistinct() GetColumnCount() int + GetColumns() SelectExprs Commented } diff --git a/go/vt/sqlparser/ast_funcs.go b/go/vt/sqlparser/ast_funcs.go index 16d70820578..5d1a6ac29ef 100644 --- a/go/vt/sqlparser/ast_funcs.go +++ b/go/vt/sqlparser/ast_funcs.go @@ -852,6 +852,11 @@ func (node *Select) SetLimit(limit *Limit) { node.Limit = limit } +// GetLimit gets the limit +func (node *Select) GetLimit() *Limit { + return node.Limit +} + // SetLock sets the lock clause func (node *Select) SetLock(lock Lock) { node.Lock = lock @@ -877,6 +882,11 @@ func (node *Select) GetColumnCount() int { return len(node.SelectExprs) } +// GetColumns gets the columns +func (node *Select) GetColumns() SelectExprs { + return node.SelectExprs +} + // SetComments implements the SelectStatement interface func (node *Select) SetComments(comments Comments) { node.Comments = comments.Parsed() @@ -964,6 +974,16 @@ func (node *Union) SetLimit(limit *Limit) { node.Limit = limit } +// GetLimit gets the limit +func (node *Union) GetLimit() *Limit { + return node.Limit +} + +// GetColumns gets the columns +func (node *Union) GetColumns() SelectExprs { + return node.Left.GetColumns() +} + // SetLock sets the lock clause func (node *Union) SetLock(lock Lock) { node.Lock = lock diff --git a/go/vt/vtgate/planbuilder/operator_to_query.go b/go/vt/vtgate/planbuilder/operator_to_query.go index f14f289a3d8..6f2ecd468c3 100644 --- a/go/vt/vtgate/planbuilder/operator_to_query.go +++ b/go/vt/vtgate/planbuilder/operator_to_query.go @@ -93,7 +93,10 @@ func buildQuery(op abstract.PhysicalOperator, qb *queryBuilder) { sel.SelectExprs = opQuery.SelectExprs qb.addTableExpr(op.Alias, op.Alias, op.TableID(), &sqlparser.DerivedTable{ Select: sel, - }, nil) + }, nil, op.ColumnAliases) + for _, col := range op.Columns { + qb.addProjection(&sqlparser.AliasedExpr{Expr: col}) + } default: panic(fmt.Sprintf("%T", op)) } @@ -120,10 +123,16 @@ func (qb *queryBuilder) addTable(db, tableName, alias string, tableID semantics. Name: sqlparser.NewTableIdent(tableName), Qualifier: sqlparser.NewTableIdent(db), } - qb.addTableExpr(tableName, alias, tableID, tableExpr, hints) + qb.addTableExpr(tableName, alias, tableID, tableExpr, hints, nil) } -func (qb *queryBuilder) addTableExpr(tableName, alias string, tableID semantics.TableSet, tblExpr sqlparser.SimpleTableExpr, hints sqlparser.IndexHints) { +func (qb *queryBuilder) addTableExpr( + tableName, alias string, + tableID semantics.TableSet, + tblExpr sqlparser.SimpleTableExpr, + hints sqlparser.IndexHints, + columnAliases sqlparser.Columns, +) { if qb.sel == nil { qb.sel = &sqlparser.Select{} } @@ -133,7 +142,7 @@ func (qb *queryBuilder) addTableExpr(tableName, alias string, tableID semantics. Partitions: nil, As: sqlparser.NewTableIdent(alias), Hints: hints, - Columns: nil, + Columns: columnAliases, } err := qb.ctx.SemTable.ReplaceTableSetFor(tableID, elems) if err != nil { diff --git a/go/vt/vtgate/planbuilder/operator_transformers.go b/go/vt/vtgate/planbuilder/operator_transformers.go index 96e0a6bdde8..b6bac24d1c7 100644 --- a/go/vt/vtgate/planbuilder/operator_transformers.go +++ b/go/vt/vtgate/planbuilder/operator_transformers.go @@ -86,13 +86,6 @@ func transformToLogicalPlan(ctx *plancontext.PlanningContext, op abstract.Physic } func transformApplyJoinPlan(ctx *plancontext.PlanningContext, n *physical.ApplyJoin) (logicalPlan, error) { - // TODO systay we should move the decision of which join to use to the greedy algorithm, - // and thus represented as a queryTree - // canHashJoin, lhsInfo, rhsInfo, err := canHashJoin(ctx, n) - // if err != nil { - // return nil, err - // } - lhs, err := transformToLogicalPlan(ctx, n.LHS, false) if err != nil { return nil, err @@ -106,23 +99,6 @@ func transformApplyJoinPlan(ctx *plancontext.PlanningContext, n *physical.ApplyJ opCode = engine.LeftJoin } - // if canHashJoin { - // coercedType, err := evalengine.CoerceTo(lhsInfo.typ.Type, rhsInfo.typ.Type) - // if err != nil { - // return nil, err - // } - // return &hashJoin{ - // Left: lhs, - // Right: rhs, - // Cols: n.columns, - // Opcode: OpCode, - // LHSKey: lhsInfo.offset, - // RHSKey: rhsInfo.offset, - // Predicate: sqlparser.AndExpressions(n.predicates...), - // ComparisonType: coercedType, - // Collation: lhsInfo.typ.Collation, - // }, nil - // } return &joinGen4{ Left: lhs, Right: rhs, diff --git a/go/vt/vtgate/planbuilder/physical/derived.go b/go/vt/vtgate/planbuilder/physical/derived.go index 813629c88d1..16f47a3dfaf 100644 --- a/go/vt/vtgate/planbuilder/physical/derived.go +++ b/go/vt/vtgate/planbuilder/physical/derived.go @@ -21,6 +21,7 @@ import ( "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/vterrors" "vitess.io/vitess/go/vt/vtgate/planbuilder/abstract" + "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" "vitess.io/vitess/go/vt/vtgate/semantics" ) @@ -110,3 +111,48 @@ func (d *Derived) findOutputColumn(name *sqlparser.ColName) (int, error) { } return 0, vterrors.NewErrorf(vtrpcpb.Code_NOT_FOUND, vterrors.BadFieldError, "Unknown column '%s' in 'field list'", name.Name.String()) } + +// IsMergeable is not a great name for this function. Suggestions for a better one are welcome! +// This function will return false if the derived table inside it has to run on the vtgate side, and so can't be merged with subqueries +// This logic can also be used to check if this is a derived table that can be had on the left hand side of a vtgate join. +// Since vtgate joins are always nested loop joins, we can't execute them on the RHS +// if they do some things, like LIMIT or GROUP BY on wrong columns +func (d *Derived) IsMergeable(ctx *plancontext.PlanningContext) bool { + validVindex := func(expr sqlparser.Expr) bool { + sc := findColumnVindex(ctx, d, expr) + return sc != nil && sc.IsUnique() + } + + if d.Query.GetLimit() != nil { + return false + } + + sel, ok := d.Query.(*sqlparser.Select) + if !ok { + return false + } + + if len(sel.GroupBy) > 0 { + // iff we are grouping, we need to check that we can perform the grouping inside a single shard, and we check that + // by checking that one of the grouping expressions used is a unique single column vindex. + // TODO: we could also support the case where all the columns of a multi-column vindex are used in the grouping + for _, gb := range sel.GroupBy { + if validVindex(gb) { + return true + } + } + return false + } + + // if we have grouping, we have already checked that it's safe, and don't need to check for aggregations + // but if we don't have groupings, we need to check if there are aggregations that will mess with us + if sqlparser.ContainsAggregation(sel.SelectExprs) { + return false + } + + if sqlparser.ContainsAggregation(sel.Having) { + return false + } + + return true +} diff --git a/go/vt/vtgate/planbuilder/physical/operator_funcs.go b/go/vt/vtgate/planbuilder/physical/operator_funcs.go index aab47c70791..bc1427e252a 100644 --- a/go/vt/vtgate/planbuilder/physical/operator_funcs.go +++ b/go/vt/vtgate/planbuilder/physical/operator_funcs.go @@ -220,7 +220,7 @@ func PushOutputColumns(ctx *plancontext.PlanningContext, op abstract.PhysicalOpe if len(columns) == 0 { return op, nil, nil } - for _, col := range columns { ///select 1 from (select * from user join user_extra) t join unsharded on t.id = unsharded.apa + for _, col := range columns { i, err := op.findOutputColumn(col) if err != nil { return nil, nil, err diff --git a/go/vt/vtgate/planbuilder/physical/route_planning.go b/go/vt/vtgate/planbuilder/physical/route_planning.go index f807000b6a6..3abaf465038 100644 --- a/go/vt/vtgate/planbuilder/physical/route_planning.go +++ b/go/vt/vtgate/planbuilder/physical/route_planning.go @@ -67,16 +67,7 @@ func CreatePhysicalOperator(ctx *plancontext.PlanningContext, opTree abstract.Lo } return mergeOrJoin(ctx, opInner, opOuter, sqlparser.SplitAndExpression(nil, op.Predicate), !op.LeftJoin) case *abstract.Derived: - opInner, err := CreatePhysicalOperator(ctx, op.Inner) - if err != nil { - return nil, err - } - return &Derived{ - Source: opInner, - Query: op.Sel, - Alias: op.Alias, - ColumnAliases: op.ColumnAliases, - }, nil + return optimizeDerived(ctx, op) case *abstract.SubQuery: return optimizeSubQuery(ctx, op) case *abstract.Vindex: @@ -784,7 +775,7 @@ func canMergeOnFilter(ctx *plancontext.PlanningContext, a, b *Route, predicate s return rVindex == lVindex } -func findColumnVindex(ctx *plancontext.PlanningContext, a *Route, exp sqlparser.Expr) vindexes.SingleColumn { +func findColumnVindex(ctx *plancontext.PlanningContext, a abstract.PhysicalOperator, exp sqlparser.Expr) vindexes.SingleColumn { _, isCol := exp.(*sqlparser.ColName) if !isCol { return nil @@ -1125,3 +1116,40 @@ func pushJoinPredicateOnDerived(ctx *plancontext.PlanningContext, exprs []sqlpar node.Source = newInner return node, nil } + +func optimizeDerived(ctx *plancontext.PlanningContext, op *abstract.Derived) (abstract.PhysicalOperator, error) { + opInner, err := CreatePhysicalOperator(ctx, op.Inner) + if err != nil { + return nil, err + } + + innerRoute, ok := opInner.(*Route) + if !ok { + return buildDerivedOp(op, opInner), nil + } + + derived := &Derived{ + Source: innerRoute.Source, + Query: op.Sel, + Alias: op.Alias, + ColumnAliases: op.ColumnAliases, + } + + if innerRoute.RouteOpCode == engine.EqualUnique { + // no need to check anything if we are sure that we will only hit a single shard + } else if !derived.IsMergeable(ctx) { + return buildDerivedOp(op, opInner), nil + } + + innerRoute.Source = derived + return innerRoute, nil +} + +func buildDerivedOp(op *abstract.Derived, opInner abstract.PhysicalOperator) *Derived { + return &Derived{ + Source: opInner, + Query: op.Sel, + Alias: op.Alias, + ColumnAliases: op.ColumnAliases, + } +} diff --git a/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt b/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt index 7d127c123a9..1339c73bcba 100644 --- a/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt +++ b/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt @@ -2264,14 +2264,56 @@ Gen4 plan same as above "Name": "user", "Sharded": true }, - "FieldQuery": "select count(*) from (select `user`.col, user_extra.extra, weight_string(user_extra.extra) from `user`, user_extra where 1 != 1) as a where 1 != 1", + "FieldQuery": "select count(*) from (select `user`.col, user_extra.extra from `user`, user_extra where 1 != 1) as a where 1 != 1", + "Query": "select count(*) from (select `user`.col, user_extra.extra from `user`, user_extra where `user`.id = user_extra.user_id order by user_extra.extra asc) as a", + "Table": "`user`, user_extra" + } + ] + } +} + +# order by inside derived tables can be ignored +"select col from (select user.col, user_extra.extra from user join user_extra on user.id = user_extra.user_id order by user_extra.extra) a" +{ + "QueryType": "SELECT", + "Original": "select col from (select user.col, user_extra.extra from user join user_extra on user.id = user_extra.user_id order by user_extra.extra) a", + "Instructions": { + "OperatorType": "SimpleProjection", + "Columns": [ + 0 + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select `user`.col, user_extra.extra, weight_string(user_extra.extra) from `user` join user_extra on `user`.id = user_extra.user_id where 1 != 1", "OrderBy": "(1|2) ASC", - "Query": "select count(*) from (select `user`.col, user_extra.extra, weight_string(user_extra.extra) from `user`, user_extra where `user`.id = user_extra.user_id order by user_extra.extra asc) as a", + "Query": "select `user`.col, user_extra.extra, weight_string(user_extra.extra) from `user` join user_extra on `user`.id = user_extra.user_id order by user_extra.extra asc", + "ResultColumns": 2, "Table": "`user`, user_extra" } ] } } +{ + "QueryType": "SELECT", + "Original": "select col from (select user.col, user_extra.extra from user join user_extra on user.id = user_extra.user_id order by user_extra.extra) a", + "Instructions": { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select col from (select `user`.col, user_extra.extra from `user`, user_extra where 1 != 1) as a where 1 != 1", + "Query": "select col from (select `user`.col, user_extra.extra from `user`, user_extra where `user`.id = user_extra.user_id order by user_extra.extra asc) as a", + "Table": "`user`, user_extra" + } +} # here we keep the order since the column is visible on the outside, and used by the orderedAggregate "select col, count(*) from (select user.col, user_extra.extra from user join user_extra on user.id = user_extra.user_id order by user_extra.extra) a group by col" @@ -2292,9 +2334,9 @@ Gen4 plan same as above "Name": "user", "Sharded": true }, - "FieldQuery": "select col, count(*) from (select `user`.col, user_extra.extra, weight_string(user_extra.extra) from `user`, user_extra where 1 != 1) as a where 1 != 1 group by col", - "OrderBy": "(1|2) ASC, 0 ASC", - "Query": "select col, count(*) from (select `user`.col, user_extra.extra, weight_string(user_extra.extra) from `user`, user_extra where `user`.id = user_extra.user_id order by user_extra.extra asc) as a group by col order by col asc", + "FieldQuery": "select col, count(*) from (select `user`.col, user_extra.extra from `user`, user_extra where 1 != 1) as a where 1 != 1 group by col", + "OrderBy": "0 ASC", + "Query": "select col, count(*) from (select `user`.col, user_extra.extra from `user`, user_extra where `user`.id = user_extra.user_id order by user_extra.extra asc) as a group by col order by col asc", "Table": "`user`, user_extra" } ] @@ -4909,3 +4951,102 @@ Gen4 error: aggregate functions take a single argument 'count(distinct user_id, } } Gen4 plan same as above + +# grouping on data from derived table +"select val1, count(*) from (select id, val1 from user where val2 < 4 order by val1 limit 2) as x group by val1" +"unsupported: cross-shard query with aggregates" +{ + "QueryType": "SELECT", + "Original": "select val1, count(*) from (select id, val1 from user where val2 \u003c 4 order by val1 limit 2) as x group by val1", + "Instructions": { + "OperatorType": "Aggregate", + "Variant": "Ordered", + "Aggregates": "count_star(1) AS count(*)", + "GroupBy": "(0|2)", + "ResultColumns": 2, + "Inputs": [ + { + "OperatorType": "Projection", + "Expressions": [ + "[COLUMN 1] as val1", + "[COLUMN 0] as count(*)", + "[COLUMN 2]" + ], + "Inputs": [ + { + "OperatorType": "Limit", + "Count": "INT64(2)", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id, val1, weight_string(val1) from `user` where 1 != 1", + "OrderBy": "(1|2) ASC, (1|2) ASC", + "Query": "select id, val1, weight_string(val1) from `user` where val2 \u003c 4 order by val1 asc, val1 asc limit :__upper_limit", + "Table": "`user`" + } + ] + } + ] + } + ] + } +} + +# Can't inline derived table when it has HAVING with aggregation function +"select * from (select id from user having count(*) = 1) s" +{ + "QueryType": "SELECT", + "Original": "select * from (select id from user having count(*) = 1) s", + "Instructions": { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select * from (select id from `user` where 1 != 1) as s where 1 != 1", + "Query": "select * from (select id from `user` having count(*) = 1) as s", + "Table": "`user`" + } +} +{ + "QueryType": "SELECT", + "Original": "select * from (select id from user having count(*) = 1) s", + "Instructions": { + "OperatorType": "SimpleProjection", + "Columns": [ + 0 + ], + "Inputs": [ + { + "OperatorType": "Filter", + "Predicate": "OFFSET(1, 'count(*)') = 1", + "Inputs": [ + { + "OperatorType": "Aggregate", + "Variant": "Scalar", + "Aggregates": "random(0) AS id, sum_count_star(1) AS count(*)", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id, count(*) from `user` where 1 != 1", + "Query": "select id, count(*) from `user`", + "Table": "`user`" + } + ] + } + ] + } + ] + } +} diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases.txt b/go/vt/vtgate/planbuilder/testdata/select_cases.txt index 488c87bba25..616ea20834d 100644 --- a/go/vt/vtgate/planbuilder/testdata/select_cases.txt +++ b/go/vt/vtgate/planbuilder/testdata/select_cases.txt @@ -3595,3 +3595,1753 @@ Gen4 plan same as above ] } } + +# Subquery with `IN` condition using columns with matching lookup vindexes +"SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (1, 2, 3))" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (1, 2, 3))", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in ::__vals", + "Table": "music", + "Values": [ + "(INT64(1), INT64(2), INT64(3))" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +Gen4 plan same as above + +# Subquery with `IN` condition using columns with matching lookup vindexes, with derived table +"SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id IN (1, 2, 3)) _inner)" +{ + "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)) _inner)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select * from (select music.id from music where 1 != 1) as _inner where 1 != 1", + "Query": "select * from (select music.id from music where music.user_id in ::__vals) as _inner", + "Table": "music", + "Values": [ + "(INT64(1), INT64(2), INT64(3))" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +{ + "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)) _inner)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select _inner.id from (select music.id from music where 1 != 1) as _inner where 1 != 1", + "Query": "select _inner.id from (select music.id from music where music.user_id in ::__vals) as _inner", + "Table": "music", + "Values": [ + "(INT64(1), INT64(2), INT64(3))" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} + +# Subquery with `IN` condition using columns with matching lookup vindexes, with inner scatter 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)" +{ + "QueryType": "SELECT", + "Original": "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)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.foo = 'bar'", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in ::__vals and :__sq_has_values1 = 1 and music.id in ::__sq1", + "Table": "music", + "Values": [ + "(INT64(3), INT64(4), INT64(5))" + ], + "Vindex": "user_index" + } + ] + } +} +{ + "QueryType": "SELECT", + "Original": "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)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.foo = 'bar'", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__sq1 and music.user_id in ::__vals", + "Table": "music", + "Values": [ + "(INT64(3), INT64(4), INT64(5))" + ], + "Vindex": "user_index" + } + ] + } +} + +# Subquery with `IN` condition using columns with matching lookup vindexes +"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" +{ + "QueryType": "SELECT", + "Original": "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", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in ::__vals", + "Table": "music", + "Values": [ + "(INT64(1), INT64(2), INT64(3))" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id = 5 and :__sq_has_values1 = 1 and music.id in ::__sq1", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + } + ] + } +} +{ + "QueryType": "SELECT", + "Original": "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", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in ::__vals", + "Table": "music", + "Values": [ + "(INT64(1), INT64(2), INT64(3))" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__sq1 and music.user_id = 5", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + } + ] + } +} + +# Subquery with `IN` condition using columns with matching lookup vindexes, but not a top level predicate +"SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (1, 2, 3)) OR music.user_id = 5" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (1, 2, 3)) OR music.user_id = 5", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in ::__vals", + "Table": "music", + "Values": [ + "(INT64(1), INT64(2), INT64(3))" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__sq1 or music.user_id = 5", + "Table": "music" + } + ] + } +} +Gen4 plan same as above + +# `IN` comparison on Vindex with `None` subquery, as routing predicate +"SELECT `music`.id FROM `music` WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (NULL)) AND music.user_id = 5" +{ + "QueryType": "SELECT", + "Original": "SELECT `music`.id FROM `music` WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (NULL)) AND music.user_id = 5", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in (null)", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id = 5 and :__sq_has_values1 = 1 and music.id in ::__sq1", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + } + ] + } +} +{ + "QueryType": "SELECT", + "Original": "SELECT `music`.id FROM `music` WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (NULL)) AND music.user_id = 5", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in (null)", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__sq1 and music.user_id = 5", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + } + ] + } +} + +# `IN` comparison on Vindex with `None` subquery, as non-routing predicate +"SELECT `music`.id FROM `music` WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (NULL)) OR music.user_id = 5" +{ + "QueryType": "SELECT", + "Original": "SELECT `music`.id FROM `music` WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (NULL)) OR music.user_id = 5", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in (null)", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__sq1 or music.user_id = 5", + "Table": "music" + } + ] + } +} +Gen4 plan same as above + +# Mergeable scatter subquery +"SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.genre = 'pop')" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.genre = 'pop')", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.genre = 'pop'", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +Gen4 plan same as above + +# Mergeable scatter subquery with `GROUP BY` on unique vindex column +"SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.genre = 'pop' GROUP BY music.id)" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.genre = 'pop' GROUP BY music.id)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1 group by music.id", + "Query": "select music.id from music where music.genre = 'pop' group by music.id", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +Gen4 plan same as above + +# Unmergeable scatter subquery with `GROUP BY` on-non vindex column +"SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.genre = 'pop' GROUP BY music.genre)" +"unsupported: in scatter query: group by column must reference column in SELECT list" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.genre = 'pop' GROUP BY music.genre)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Aggregate", + "Variant": "Ordered", + "Aggregates": "random(0) AS id", + "GroupBy": "(1|2)", + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id, music.genre, weight_string(music.genre) from music where 1 != 1 group by music.genre, weight_string(music.genre)", + "OrderBy": "(1|2) ASC", + "Query": "select music.id, music.genre, weight_string(music.genre) from music where music.genre = 'pop' group by music.genre, weight_string(music.genre) order by music.genre asc", + "Table": "music" + } + ] + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} + +# Unmergeable scatter subquery with LIMIT +"SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.genre = 'pop' LIMIT 10)" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.genre = 'pop' LIMIT 10)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Limit", + "Count": "INT64(10)", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.genre = 'pop' limit :__upper_limit", + "Table": "music" + } + ] + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +Gen4 plan same as above + +# Mergeable subquery with `MAX` aggregate and grouped by unique vindex +"SELECT music.id FROM music WHERE music.id IN (SELECT MAX(music.id) FROM music WHERE music.user_id IN (5, 6) GROUP BY music.user_id)" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT MAX(music.id) FROM music WHERE music.user_id IN (5, 6) GROUP BY music.user_id)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select MAX(music.id) from music where 1 != 1 group by music.user_id", + "Query": "select MAX(music.id) from music where music.user_id in ::__vals group by music.user_id", + "Table": "music", + "Values": [ + "(INT64(5), INT64(6))" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +Gen4 plan same as above + +# Unmergeable subquery with `MAX` aggregate +"SELECT music.id FROM music WHERE music.id IN (SELECT MAX(music.id) FROM music WHERE music.user_id IN (5, 6))" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT MAX(music.id) FROM music WHERE music.user_id IN (5, 6))", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Aggregate", + "Variant": "Scalar", + "Aggregates": "max(0)", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select MAX(music.id) from music where 1 != 1", + "Query": "select MAX(music.id) from music where music.user_id in ::__vals", + "Table": "music", + "Values": [ + "(INT64(5), INT64(6))" + ], + "Vindex": "user_index" + } + ] + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT MAX(music.id) FROM music WHERE music.user_id IN (5, 6))", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Aggregate", + "Variant": "Scalar", + "Aggregates": "max(0) AS MAX(music.id)", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select MAX(music.id) from music where 1 != 1", + "Query": "select MAX(music.id) from music where music.user_id in ::__vals", + "Table": "music", + "Values": [ + "(INT64(5), INT64(6))" + ], + "Vindex": "user_index" + } + ] + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} + +# Mergeable subquery with `MAX` aggregate with `EqualUnique` route operator +"SELECT music.id FROM music WHERE music.id IN (SELECT MAX(music.id) FROM music WHERE music.user_id = 5)" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT MAX(music.id) FROM music WHERE music.user_id = 5)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select MAX(music.id) from music where 1 != 1", + "Query": "select MAX(music.id) from music where music.user_id = 5", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +Gen4 plan same as above + +# Mergeable subquery with `LIMIT` due to `EqualUnique` route +"SELECT music.id FROM music WHERE music.id IN (SELECT MAX(music.id) FROM music WHERE music.user_id = 5 LIMIT 10)" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT MAX(music.id) FROM music WHERE music.user_id = 5 LIMIT 10)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select MAX(music.id) from music where 1 != 1", + "Query": "select MAX(music.id) from music where music.user_id = 5 limit 10", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +Gen4 plan same as above + +# Mergeable subquery with multiple levels of derived statements +"SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id = 5 LIMIT 10) subquery_for_limit) subquery_for_limit)" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id = 5 LIMIT 10) subquery_for_limit) subquery_for_limit)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select * from (select * from (select music.id from music where 1 != 1) as subquery_for_limit where 1 != 1) as subquery_for_limit where 1 != 1", + "Query": "select * from (select * from (select music.id from music where music.user_id = 5 limit 10) as subquery_for_limit) as subquery_for_limit", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id = 5 LIMIT 10) subquery_for_limit) subquery_for_limit)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select subquery_for_limit.id from (select subquery_for_limit.id from (select music.id from music where 1 != 1) as subquery_for_limit where 1 != 1) as subquery_for_limit where 1 != 1", + "Query": "select subquery_for_limit.id from (select subquery_for_limit.id from (select music.id from music where music.user_id = 5 limit 10) as subquery_for_limit) as subquery_for_limit", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} + +# Mergeable subquery with multiple levels of derived statements, using a single value `IN` predicate +"SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id IN (5) LIMIT 10) subquery_for_limit) subquery_for_limit)" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id IN (5) LIMIT 10) subquery_for_limit) subquery_for_limit)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "SimpleProjection", + "Columns": [ + 0 + ], + "Inputs": [ + { + "OperatorType": "SimpleProjection", + "Columns": [ + 0 + ], + "Inputs": [ + { + "OperatorType": "Limit", + "Count": "INT64(10)", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in ::__vals limit :__upper_limit", + "Table": "music", + "Values": [ + "(INT64(5))" + ], + "Vindex": "user_index" + } + ] + } + ] + } + ] + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +Gen4 plan same as above + +# Unmergeable subquery with multiple levels of derived statements, using a multi value `IN` predicate +"SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id IN (5, 6) LIMIT 10) subquery_for_limit) subquery_for_limit)" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT * FROM (SELECT music.id FROM music WHERE music.user_id IN (5, 6) LIMIT 10) subquery_for_limit) subquery_for_limit)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "SimpleProjection", + "Columns": [ + 0 + ], + "Inputs": [ + { + "OperatorType": "SimpleProjection", + "Columns": [ + 0 + ], + "Inputs": [ + { + "OperatorType": "Limit", + "Count": "INT64(10)", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in ::__vals limit :__upper_limit", + "Table": "music", + "Values": [ + "(INT64(5), INT64(6))" + ], + "Vindex": "user_index" + } + ] + } + ] + } + ] + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +Gen4 plan same as above + +# Unmergeable subquery with multiple levels of derived statements +"SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT * FROM (SELECT music.id FROM music LIMIT 10) subquery_for_limit) subquery_for_limit)" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT * FROM (SELECT * FROM (SELECT music.id FROM music LIMIT 10) subquery_for_limit) subquery_for_limit)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "SimpleProjection", + "Columns": [ + 0 + ], + "Inputs": [ + { + "OperatorType": "SimpleProjection", + "Columns": [ + 0 + ], + "Inputs": [ + { + "OperatorType": "Limit", + "Count": "INT64(10)", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music limit :__upper_limit", + "Table": "music" + } + ] + } + ] + } + ] + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +Gen4 plan same as above + +# `None` subquery as top level predicate - outer query changes from `Scatter` to `None` on merge +"SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (NULL))" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (NULL))", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in (null)", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__vals", + "Table": "music", + "Values": [ + ":__sq1" + ], + "Vindex": "music_user_map" + } + ] + } +} +Gen4 plan same as above + +# `None` subquery as top level predicate - outer query changes from `EqualUnique` to `None` on merge +"SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (NULL)) AND music.user_id = 5" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (NULL)) AND music.user_id = 5", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in (null)", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id = 5 and :__sq_has_values1 = 1 and music.id in ::__sq1", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + } + ] + } +} +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE music.id IN (SELECT music.id FROM music WHERE music.user_id IN (NULL)) AND music.user_id = 5", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in (null)", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__sq1 and music.user_id = 5", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + } + ] + } +} + +# `None` subquery nested inside `OR` expression - outer query keeps routing information +"SELECT music.id FROM music WHERE (music.id IN (SELECT music.id FROM music WHERE music.user_id IN (NULL)) OR music.user_id = 5)" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music WHERE (music.id IN (SELECT music.id FROM music WHERE music.user_id IN (NULL)) OR music.user_id = 5)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.user_id in (null)", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where :__sq_has_values1 = 1 and music.id in ::__sq1 or music.user_id = 5", + "Table": "music" + } + ] + } +} +Gen4 plan same as above + +# Joining with a subquery that uses an aggregate column and an `EqualUnique` route can be merged together +"SELECT music.id FROM music INNER JOIN (SELECT MAX(id) as maxt FROM music WHERE music.user_id = 5) other ON other.maxt = music.id" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music INNER JOIN (SELECT MAX(id) as maxt FROM music WHERE music.user_id = 5) other ON other.maxt = music.id", + "Instructions": { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "L:0", + "JoinVars": { + "music_id": 0 + }, + "TableName": "music_music", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select 1 from (select MAX(id) as maxt from music where 1 != 1) as other where 1 != 1", + "Query": "select 1 from (select MAX(id) as maxt from music where music.user_id = 5) as other where other.maxt = :music_id", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + } + ] + } +} +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music INNER JOIN (SELECT MAX(id) as maxt FROM music WHERE music.user_id = 5) other ON other.maxt = music.id", + "Instructions": { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "L:0", + "JoinVars": { + "music_id": 0 + }, + "TableName": "music_music", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select 1 from (select MAX(id) as maxt from music where 1 != 1) as other where 1 != 1", + "Query": "select 1 from (select MAX(id) as maxt from music where music.user_id = 5 and MAX(id) = :music_id) as other", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + } + ] + } +} + +# Joining with a subquery that uses an `EqualUnique` route can be merged +"SELECT music.id FROM music INNER JOIN (SELECT id FROM music WHERE music.user_id = 5) other ON other.id = music.id" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music INNER JOIN (SELECT id FROM music WHERE music.user_id = 5) other ON other.id = music.id", + "Instructions": { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music join (select id from music where 1 != 1) as other on other.id = music.id where 1 != 1", + "Query": "select music.id from music join (select id from music where music.user_id = 5) as other on other.id = music.id", + "Table": "music" + } +} +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music INNER JOIN (SELECT id FROM music WHERE music.user_id = 5) other ON other.id = music.id", + "Instructions": { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music, (select id from music where 1 != 1) as other where 1 != 1", + "Query": "select music.id from music, (select id from music where music.user_id = 5) as other where other.id = music.id", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + } +} + +# Joining with a subquery that has an `IN` route can be merged +"SELECT music.id FROM music INNER JOIN (SELECT id FROM music WHERE music.user_id IN (5, 6, 7)) other ON other.id = music.id" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music INNER JOIN (SELECT id FROM music WHERE music.user_id IN (5, 6, 7)) other ON other.id = music.id", + "Instructions": { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music join (select id from music where 1 != 1) as other on other.id = music.id where 1 != 1", + "Query": "select music.id from music join (select id from music where music.user_id in (5, 6, 7)) as other on other.id = music.id", + "Table": "music" + } +} +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM music INNER JOIN (SELECT id FROM music WHERE music.user_id IN (5, 6, 7)) other ON other.id = music.id", + "Instructions": { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music, (select id from music where 1 != 1) as other where 1 != 1", + "Query": "select music.id from music, (select id from music where music.user_id in ::__vals) as other where other.id = music.id", + "Table": "music", + "Values": [ + "(INT64(5), INT64(6), INT64(7))" + ], + "Vindex": "user_index" + } +} + +# limit on the vtgate has to be executed on the LHS of a join +"select id from user join (select user_id from user_extra limit 10) ue on user.id = ue.user_id" +"unsupported: filtering on results of cross-shard subquery" +{ + "QueryType": "SELECT", + "Original": "select id from user join (select user_id from user_extra limit 10) ue on user.id = ue.user_id", + "Instructions": { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from `user`, (select user_id from user_extra where 1 != 1) as ue where 1 != 1", + "Query": "select id from `user`, (select user_id from user_extra limit 10) as ue where `user`.id = ue.user_id", + "Table": "`user`, user_extra" + } +} + +"select user.a, t.b from user join (select id, count(*) b, req from user_extra group by req, id) as t on user.id = t.id" +"unsupported: filtering on results of cross-shard subquery" +{ + "QueryType": "SELECT", + "Original": "select user.a, t.b from user join (select id, count(*) b, req from user_extra group by req, id) as t on user.id = t.id", + "Instructions": { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "L:1,R:0", + "JoinVars": { + "user_id": 0 + }, + "TableName": "`user`_user_extra", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select `user`.id, `user`.a from `user` where 1 != 1", + "Query": "select `user`.id, `user`.a from `user`", + "Table": "`user`" + }, + { + "OperatorType": "SimpleProjection", + "Columns": [ + 1 + ], + "Inputs": [ + { + "OperatorType": "Aggregate", + "Variant": "Ordered", + "Aggregates": "sum_count_star(1) AS b", + "GroupBy": "(0|3), (2|4)", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id, count(*) as b, req, weight_string(id), weight_string(req) from user_extra where 1 != 1 group by id, weight_string(id), req, weight_string(req)", + "OrderBy": "(0|3) ASC, (2|4) ASC", + "Query": "select id, count(*) as b, req, weight_string(id), weight_string(req) from user_extra where id = :user_id group by id, weight_string(id), req, weight_string(req) order by id asc, req asc", + "Table": "user_extra" + } + ] + } + ] + } + ] + } +} + +# cant switch sides for outer joins +"select id from user left join (select user_id from user_extra limit 10) ue on user.id = ue.user_id" +{ + "QueryType": "SELECT", + "Original": "select id from user left join (select user_id from user_extra limit 10) ue on user.id = ue.user_id", + "Instructions": { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from `user` left join (select user_id from user_extra where 1 != 1) as ue on `user`.id = ue.user_id where 1 != 1", + "Query": "select id from `user` left join (select user_id from user_extra limit 10) as ue on `user`.id = ue.user_id", + "Table": "`user`, user_extra" + } +} +Gen4 plan same as above + +# limit on both sides means that we can't evaluate this at all +"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" +"unsupported: filtering on results of cross-shard subquery" +{ + "QueryType": "SELECT", + "Original": "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", + "Instructions": { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from (select id from `user` where 1 != 1) as u, (select user_id from user_extra where 1 != 1) as ue where 1 != 1", + "Query": "select id from (select id from `user` limit 10) as u, (select user_id from user_extra limit 10) as ue where u.id = ue.user_id", + "Table": "`user`, user_extra" + } +} + +"SELECT music.id FROM (SELECT MAX(id) as maxt FROM music WHERE music.user_id = 5) other JOIN music ON other.maxt = music.id" +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM (SELECT MAX(id) as maxt FROM music WHERE music.user_id = 5) other JOIN music ON other.maxt = music.id", + "Instructions": { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "R:0", + "JoinVars": { + "other_maxt": 0 + }, + "TableName": "music_music", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select other.maxt from (select MAX(id) as maxt from music where 1 != 1) as other where 1 != 1", + "Query": "select other.maxt from (select MAX(id) as maxt from music where music.user_id = 5) as other", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.id = :other_maxt", + "Table": "music", + "Values": [ + ":other_maxt" + ], + "Vindex": "music_user_map" + } + ] + } +} +{ + "QueryType": "SELECT", + "Original": "SELECT music.id FROM (SELECT MAX(id) as maxt FROM music WHERE music.user_id = 5) other JOIN music ON other.maxt = music.id", + "Instructions": { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "R:0", + "JoinVars": { + "other_maxt": 0 + }, + "TableName": "music_music", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select 1 from (select MAX(id) as maxt from music where 1 != 1) as other where 1 != 1", + "Query": "select 1 from (select MAX(id) as maxt from music where music.user_id = 5) as other", + "Table": "music", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select music.id from music where 1 != 1", + "Query": "select music.id from music where music.id = :other_maxt", + "Table": "music", + "Values": [ + ":other_maxt" + ], + "Vindex": "music_user_map" + } + ] + } +}