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 7006f3980fa..3b44811f95c 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-"}, 0, false) 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 90ddab990be..686aa4ea089 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 112e6b34386..8ece2749788 100644 --- a/go/vt/sqlparser/ast_funcs.go +++ b/go/vt/sqlparser/ast_funcs.go @@ -918,6 +918,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 @@ -943,6 +948,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() @@ -1030,6 +1040,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 03161e7f1d0..87dec2d8526 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.NewIdentifierCS(tableName), Qualifier: sqlparser.NewIdentifierCS(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.NewIdentifierCS(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 efffbd60f98..5f6d0e5da96 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 f61bbc26fb7..16757c041b1 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 bdf67c3f8ea..2f3e664129f 100644 --- a/go/vt/vtgate/planbuilder/physical/route_planning.go +++ b/go/vt/vtgate/planbuilder/physical/route_planning.go @@ -48,33 +48,11 @@ type ( func CreatePhysicalOperator(ctx *plancontext.PlanningContext, opTree abstract.LogicalOperator) (abstract.PhysicalOperator, error) { switch op := opTree.(type) { case *abstract.QueryGraph: - switch { - case ctx.PlannerVersion == querypb.ExecuteOptions_Gen4Left2Right: - return leftToRightSolve(ctx, op) - default: - return greedySolve(ctx, op) - } + return optimizeQueryGraph(ctx, op) case *abstract.Join: - opInner, err := CreatePhysicalOperator(ctx, op.LHS) - if err != nil { - return nil, err - } - opOuter, err := CreatePhysicalOperator(ctx, op.RHS) - if err != nil { - return nil, err - } - return mergeOrJoin(ctx, opInner, opOuter, sqlparser.SplitAndExpression(nil, op.Predicate), !op.LeftJoin) + return optimizeJoin(ctx, op) 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: @@ -82,25 +60,7 @@ func CreatePhysicalOperator(ctx *plancontext.PlanningContext, opTree abstract.Lo case *abstract.Concatenate: return optimizeUnion(ctx, op) case *abstract.Filter: - src, err := CreatePhysicalOperator(ctx, op.Source) - if err != nil { - return nil, err - } - - filter := &Filter{ - Predicates: op.Predicates, - } - - if route, ok := src.(*Route); ok { - // let's push the filter into the route - filter.Source = route.Source - route.Source = filter - return route, nil - } - - filter.Source = src - - return filter, nil + return optimizeFilter(ctx, op) case *abstract.Update: return createPhysicalOperatorFromUpdate(ctx, op) case *abstract.Delete: @@ -110,6 +70,87 @@ func CreatePhysicalOperator(ctx *plancontext.PlanningContext, opTree abstract.Lo } } +func optimizeFilter(ctx *plancontext.PlanningContext, op *abstract.Filter) (abstract.PhysicalOperator, error) { + src, err := CreatePhysicalOperator(ctx, op.Source) + if err != nil { + return nil, err + } + + filter := &Filter{ + Predicates: op.Predicates, + } + + if route, ok := src.(*Route); ok { + // let's push the filter into the route + filter.Source = route.Source + route.Source = filter + return route, nil + } + + filter.Source = src + + return filter, 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, + } +} + +func optimizeJoin(ctx *plancontext.PlanningContext, op *abstract.Join) (abstract.PhysicalOperator, error) { + lhs, err := CreatePhysicalOperator(ctx, op.LHS) + if err != nil { + return nil, err + } + rhs, err := CreatePhysicalOperator(ctx, op.RHS) + if err != nil { + return nil, err + } + + return mergeOrJoin(ctx, lhs, rhs, sqlparser.SplitAndExpression(nil, op.Predicate), !op.LeftJoin) +} + +func optimizeQueryGraph(ctx *plancontext.PlanningContext, op *abstract.QueryGraph) (abstract.PhysicalOperator, error) { + switch { + case ctx.PlannerVersion == querypb.ExecuteOptions_Gen4Left2Right: + return leftToRightSolve(ctx, op) + default: + return greedySolve(ctx, op) + } +} + func createPhysicalOperatorFromUpdate(ctx *plancontext.PlanningContext, op *abstract.Update) (abstract.PhysicalOperator, error) { vindexTable, opCode, dest, err := buildVindexTableForDML(ctx, op.TableInfo, op.Table, "update") if err != nil { @@ -598,8 +639,25 @@ func getJoinFor(ctx *plancontext.PlanningContext, cm opCacheMap, lhs, rhs abstra return join, nil } -func mergeOrJoin(ctx *plancontext.PlanningContext, lhs, rhs abstract.PhysicalOperator, joinPredicates []sqlparser.Expr, inner bool) (abstract.PhysicalOperator, error) { +func requiresSwitchingSides(ctx *plancontext.PlanningContext, op abstract.PhysicalOperator) bool { + required := false + + _ = VisitOperators(op, func(current abstract.PhysicalOperator) (bool, error) { + derived, isDerived := current.(*Derived) + + if isDerived && !derived.IsMergeable(ctx) { + required = true + + return false, nil + } + + return true, nil + }) + + return required +} +func mergeOrJoin(ctx *plancontext.PlanningContext, lhs, rhs abstract.PhysicalOperator, joinPredicates []sqlparser.Expr, inner bool) (abstract.PhysicalOperator, error) { merger := func(a, b *Route) (*Route, error) { return createRouteOperatorForJoin(a, b, joinPredicates, inner) } @@ -609,6 +667,25 @@ func mergeOrJoin(ctx *plancontext.PlanningContext, lhs, rhs abstract.PhysicalOpe return newPlan, nil } + if len(joinPredicates) > 0 && requiresSwitchingSides(ctx, rhs) { + if !inner { + return nil, vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "unsupported: LEFT JOIN not supported for derived tables") + } + + if requiresSwitchingSides(ctx, lhs) { + return nil, vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "unsupported: JOIN not supported between derived tables") + } + + join := &ApplyJoin{ + LHS: rhs.Clone(), + RHS: lhs.Clone(), + Vars: map[string]int{}, + LeftJoin: !inner, + } + + return pushJoinPredicates(ctx, joinPredicates, join) + } + join := &ApplyJoin{ LHS: lhs.Clone(), RHS: rhs.Clone(), @@ -655,38 +732,13 @@ func createRouteOperatorForJoin(aRoute, bRoute *Route, joinPredicates []sqlparse type mergeFunc func(a, b *Route) (*Route, error) -// makeRoute return the input as a Route. -// if the input is a Derived operator and has a Route as its source, -// we push the Derived inside the Route and return it. -func makeRoute(j abstract.PhysicalOperator) *Route { - route, ok := j.(*Route) - if ok { - return route - } - - derived, ok := j.(*Derived) - if !ok { - return nil - } - dp := derived.Clone().(*Derived) - - route = makeRoute(dp.Source) - if route == nil { - return nil - } - - derived.Source = route.Source - route.Source = derived - return route -} - func operatorsToRoutes(a, b abstract.PhysicalOperator) (*Route, *Route) { - aRoute := makeRoute(a) - if aRoute == nil { + aRoute, ok := a.(*Route) + if !ok { return nil, nil } - bRoute := makeRoute(b) - if bRoute == nil { + bRoute, ok := b.(*Route) + if !ok { return nil, nil } return aRoute, bRoute @@ -867,7 +919,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 @@ -1132,7 +1184,8 @@ func pushJoinPredicateOnJoin(ctx *plancontext.PlanningContext, exprs []sqlparser // rows as early as possible making join cheaper on the vtgate level. depsForExpr := ctx.SemTable.RecursiveDeps(expr) singleSideDeps := false - if depsForExpr.IsSolvedBy(node.LHS.TableID()) { + lhsTables := node.LHS.TableID() + if depsForExpr.IsSolvedBy(lhsTables) { lhsPreds = append(lhsPreds, expr) singleSideDeps = true } @@ -1145,7 +1198,7 @@ func pushJoinPredicateOnJoin(ctx *plancontext.PlanningContext, exprs []sqlparser continue } - bvName, cols, predicate, err := BreakExpressionInLHSandRHS(ctx, expr, node.LHS.TableID()) + bvName, cols, predicate, err := BreakExpressionInLHSandRHS(ctx, expr, lhsTables) if err != nil { return nil, err } diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go index e1151edda91..8ac609dc4e4 100644 --- a/go/vt/vtgate/planbuilder/plan_test.go +++ b/go/vt/vtgate/planbuilder/plan_test.go @@ -278,7 +278,7 @@ func TestOne(t *testing.T) { v: loadSchema(t, "schema_test.json", true), } - testFile(t, "onecase.txt", "", vschema, true) + testFile(t, "onecase.txt", "", vschema, false) } func TestOneWithMainAsDefault(t *testing.T) { diff --git a/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt b/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt index 15d7a04d50d..d1c54cb1b2d 100644 --- a/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt +++ b/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt @@ -2714,12 +2714,58 @@ 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" + } + ] + }, + "TablesUsed": [ + "user.user", + "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" }, "TablesUsed": [ "user.user", @@ -2727,6 +2773,7 @@ Gen4 plan same as above ] } + # 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" "unsupported: cross-shard query with aggregates" @@ -2746,9 +2793,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" } ] @@ -5670,3 +5717,60 @@ Gen4 plan same as above "user.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": ":1 = 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`" + } + ] + } + ] + } + ] + }, + "TablesUsed": [ + "user.user" + ] +} diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases.txt b/go/vt/vtgate/planbuilder/testdata/select_cases.txt index bebdde53820..daff60a8b65 100644 --- a/go/vt/vtgate/planbuilder/testdata/select_cases.txt +++ b/go/vt/vtgate/planbuilder/testdata/select_cases.txt @@ -5656,3 +5656,2472 @@ Gen4 plan same as above "user.music" ] } + +# 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" + } + ] + } +} +{ + "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + } +} +{ + "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# `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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# `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" + } + ] + } +} +{ + "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + } +} +{ + "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + } +} +{ + "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + } +} +{ + "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + } +} +{ + "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + } +} +{ + "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + } +} +{ + "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + } +} +{ + "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": "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 in (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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + } +} +{ + "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + } + ] + } +} +{ + "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# `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" + } + ] + } +} +{ + "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# `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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# `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" + } + ] + } +} +{ + "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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": "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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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" + }, + "TablesUsed": [ + "user.music" + ] +} + +# 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": "Join", + "Variant": "Join", + "JoinColumnIndexes": "R:0", + "JoinVars": { + "ue_user_id": 0 + }, + "TableName": "user_extra_`user`", + "Inputs": [ + { + "OperatorType": "SimpleProjection", + "Columns": [ + 0 + ], + "Inputs": [ + { + "OperatorType": "Limit", + "Count": "INT64(10)", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select user_id from user_extra where 1 != 1", + "Query": "select user_id from user_extra limit :__upper_limit", + "Table": "user_extra" + } + ] + } + ] + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from `user` where 1 != 1", + "Query": "select id from `user` where `user`.id = :ue_user_id", + "Table": "`user`", + "Values": [ + ":ue_user_id" + ], + "Vindex": "user_index" + } + ] + }, + "TablesUsed": [ + "user.user", + "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": "R:0,L:1", + "JoinVars": { + "t_id": 0 + }, + "TableName": "user_extra_`user`", + "Inputs": [ + { + "OperatorType": "SimpleProjection", + "Columns": [ + 0, + 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 group by id, weight_string(id), req, weight_string(req) order by id asc, req asc", + "Table": "user_extra" + } + ] + } + ] + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select `user`.a from `user` where 1 != 1", + "Query": "select `user`.a from `user` where `user`.id = :t_id", + "Table": "`user`", + "Values": [ + ":t_id" + ], + "Vindex": "user_index" + } + ] + }, + "TablesUsed": [ + "user.user", + "user.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" +"unsupported: LEFT JOIN not supported for derived tables" +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" +Gen4 error: unsupported: JOIN not supported between derived tables + +"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" + } + ] + }, + "TablesUsed": [ + "user.music" + ] +}