diff --git a/go/vt/sqlparser/ast_rewriting.go b/go/vt/sqlparser/ast_rewriting.go index ba8189b7f53..e594c200851 100644 --- a/go/vt/sqlparser/ast_rewriting.go +++ b/go/vt/sqlparser/ast_rewriting.go @@ -264,19 +264,19 @@ func newExpressionRewriter(keyspace string, selectLimit int) *expressionRewriter } const ( - //LastInsertIDName is a reserved bind var name for last_insert_id() + // LastInsertIDName is a reserved bind var name for last_insert_id() LastInsertIDName = "__lastInsertId" - //DBVarName is a reserved bind var name for database() + // DBVarName is a reserved bind var name for database() DBVarName = "__vtdbname" - //FoundRowsName is a reserved bind var name for found_rows() + // FoundRowsName is a reserved bind var name for found_rows() FoundRowsName = "__vtfrows" - //RowCountName is a reserved bind var name for row_count() + // RowCountName is a reserved bind var name for row_count() RowCountName = "__vtrcount" - //UserDefinedVariableName is what we prepend bind var names for user defined variables + // UserDefinedVariableName is what we prepend bind var names for user defined variables UserDefinedVariableName = "__vtudv" ) @@ -514,6 +514,9 @@ func (er *expressionRewriter) funcRewrite(cursor *Cursor, node *FuncExpr) { } func (er *expressionRewriter) unnestSubQueries(cursor *Cursor, subquery *Subquery) { + if _, isExists := cursor.Parent().(*ExistsExpr); isExists { + return + } sel, isSimpleSelect := subquery.Select.(*Select) if !isSimpleSelect { return diff --git a/go/vt/sqlparser/ast_rewriting_test.go b/go/vt/sqlparser/ast_rewriting_test.go index f0d72c1ebbf..4d1b68bcd81 100644 --- a/go/vt/sqlparser/ast_rewriting_test.go +++ b/go/vt/sqlparser/ast_rewriting_test.go @@ -154,6 +154,9 @@ func TestRewrites(in *testing.T) { }, { in: "select (select 42) from dual", expected: "select 42 as `(select 42 from dual)` from dual", + }, { + in: "select exists(select 1) from user", + expected: "select exists(select 1) from user", }, { in: "select * from user where col = (select 42)", expected: "select * from user where col = 42", diff --git a/go/vt/vtgate/planbuilder/physical/route_planning.go b/go/vt/vtgate/planbuilder/physical/route_planning.go index 1f8fd8535a4..3a81f75767d 100644 --- a/go/vt/vtgate/planbuilder/physical/route_planning.go +++ b/go/vt/vtgate/planbuilder/physical/route_planning.go @@ -558,17 +558,22 @@ func leaves(op abstract.Operator) (sources []abstract.Operator) { } func tryMergeReferenceTable(aRoute, bRoute *Route, merger mergeFunc) (*Route, error) { - // if either side is a reference table, we can just merge it and use the opcode of the other side - var opCode engine.Opcode - var selected *VindexOption + var ( + // if either side is a reference table, we can just merge it and use the opcode of the other side + opCode engine.Opcode + vindex *VindexOption + ks *vindexes.Keyspace + ) switch { case aRoute.RouteOpCode == engine.Reference: - selected = bRoute.Selected + vindex = bRoute.Selected opCode = bRoute.RouteOpCode + ks = bRoute.Keyspace case bRoute.RouteOpCode == engine.Reference: - selected = aRoute.Selected + vindex = aRoute.Selected opCode = aRoute.RouteOpCode + ks = aRoute.Keyspace default: return nil, nil } @@ -578,7 +583,8 @@ func tryMergeReferenceTable(aRoute, bRoute *Route, merger mergeFunc) (*Route, er return nil, err } r.RouteOpCode = opCode - r.Selected = selected + r.Selected = vindex + r.Keyspace = ks return r, nil } diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go index f5106c00b39..a3cc5a585f7 100644 --- a/go/vt/vtgate/planbuilder/plan_test.go +++ b/go/vt/vtgate/planbuilder/plan_test.go @@ -258,6 +258,30 @@ func TestOne(t *testing.T) { testFile(t, "onecase.txt", "", vschema) } +func TestOneWithMainAsDefault(t *testing.T) { + vschema := &vschemaWrapper{ + v: loadSchema(t, "schema_test.json", true), + keyspace: &vindexes.Keyspace{ + Name: "main", + Sharded: false, + }, + } + + testFile(t, "onecase.txt", "", vschema) +} + +func TestOneWithSecondUserAsDefault(t *testing.T) { + vschema := &vschemaWrapper{ + v: loadSchema(t, "schema_test.json", true), + keyspace: &vindexes.Keyspace{ + Name: "second_user", + Sharded: true, + }, + } + + testFile(t, "onecase.txt", "", vschema) +} + func TestRubyOnRailsQueries(t *testing.T) { vschemaWrapper := &vschemaWrapper{ v: loadSchema(t, "rails_schema_test.json", true), @@ -372,6 +396,21 @@ func TestWithDefaultKeyspaceFromFile(t *testing.T) { testFile(t, "call_cases.txt", testOutputTempDir, vschema) } +func TestWithDefaultKeyspaceFromFileSharded(t *testing.T) { + // We are testing this separately so we can set a default keyspace + vschema := &vschemaWrapper{ + v: loadSchema(t, "schema_test.json", true), + keyspace: &vindexes.Keyspace{ + Name: "second_user", + Sharded: true, + }, + tabletType: topodatapb.TabletType_PRIMARY, + } + + testOutputTempDir := makeTestOutput(t) + testFile(t, "select_cases_with_default.txt", testOutputTempDir, vschema) +} + func TestWithSystemSchemaAsDefaultKeyspace(t *testing.T) { // We are testing this separately so we can set a default keyspace vschema := &vschemaWrapper{ diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases.txt b/go/vt/vtgate/planbuilder/testdata/select_cases.txt index 9a07140afeb..20580da368c 100644 --- a/go/vt/vtgate/planbuilder/testdata/select_cases.txt +++ b/go/vt/vtgate/planbuilder/testdata/select_cases.txt @@ -2207,8 +2207,8 @@ Gen4 plan same as above "OperatorType": "Route", "Variant": "Scatter", "Keyspace": { - "Name": "main", - "Sharded": false + "Name": "user", + "Sharded": true }, "FieldQuery": "select 42, id from dual, `user` where 1 != 1", "Query": "select 42, id from dual, `user`", @@ -2940,3 +2940,65 @@ Gen4 plan same as above } } Gen4 plan same as above + +# yeah, it does not make sense, but it's valid +"select exists(select 1) from user where id = 5" +{ + "QueryType": "SELECT", + "Original": "select exists(select 1) from user where id = 5", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutExists", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Reference", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select 1 from dual where 1 != 1", + "Query": "select 1 from dual", + "Table": "dual" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select :__sq_has_values1 from `user` where 1 != 1", + "Query": "select :__sq_has_values1 from `user` where id = 5", + "Table": "`user`", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + } + ] + } +} +{ + "QueryType": "SELECT", + "Original": "select exists(select 1) from user where id = 5", + "Instructions": { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select exists (select 1 from dual where 1 != 1) from `user` where 1 != 1", + "Query": "select exists (select 1 from dual) from `user` where id = 5", + "Table": "`user`", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + } +} diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases_with_default.txt b/go/vt/vtgate/planbuilder/testdata/select_cases_with_default.txt new file mode 100644 index 00000000000..057916f9a61 --- /dev/null +++ b/go/vt/vtgate/planbuilder/testdata/select_cases_with_default.txt @@ -0,0 +1,61 @@ +# EXISTS subquery when the default ks is different than the inner query +"select exists(select * from user where id = 5)" +{ + "QueryType": "SELECT", + "Original": "select exists(select * from user where id = 5)", + "Instructions": { + "OperatorType": "Subquery", + "Variant": "PulloutExists", + "PulloutVars": [ + "__sq_has_values1", + "__sq1" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select * from `user` where 1 != 1", + "Query": "select * from `user` where id = 5", + "Table": "`user`", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "Reference", + "Keyspace": { + "Name": "second_user", + "Sharded": true + }, + "FieldQuery": "select :__sq_has_values1 from dual where 1 != 1", + "Query": "select :__sq_has_values1 from dual", + "Table": "dual" + } + ] + } +} +{ + "QueryType": "SELECT", + "Original": "select exists(select * from user where id = 5)", + "Instructions": { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select exists (select * from `user` where 1 != 1) from dual where 1 != 1", + "Query": "select exists (select * from `user` where id = 5) from dual", + "Table": "dual", + "Values": [ + "INT64(5)" + ], + "Vindex": "user_index" + } +}