Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Improve sharded query routing for tuple list #14892

Merged
merged 6 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions go/vt/vtgate/engine/route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1716,3 +1716,46 @@ func TestBuildMultiColumnVindexValues(t *testing.T) {
})
}
}

// TestSelectTupleMultiCol tests route execution having bind variable with multi column tuple.
func TestSelectTupleMultiCol(t *testing.T) {
vindex, _ := vindexes.CreateVindex("multicol", "", map[string]string{
"column_count": "2",
"column_vindex": "hash,binary",
})

sel := NewRoute(
MultiEqual,
&vindexes.Keyspace{Name: "user", Sharded: true},
"select 1 from multicol_tbl where (colb, colx, cola) in ::vals",
"select 1 from multicol_tbl where 1 != 1",
)
sel.Vindex = vindex
sel.Values = []evalengine.Expr{
&evalengine.TupleBindVariable{Key: "vals", Index: 0},
&evalengine.TupleBindVariable{Key: "vals", Index: 1},
}

v1 := sqltypes.TestTuple(sqltypes.NewInt64(1), sqltypes.NewVarChar("a"))
v2 := sqltypes.TestTuple(sqltypes.NewInt64(4), sqltypes.NewVarChar("b"))
tupleBV := &querypb.BindVariable{
Type: querypb.Type_TUPLE,
Values: append([]*querypb.Value{sqltypes.ValueToProto(v1)}, sqltypes.ValueToProto(v2)),
}
vc := &loggingVCursor{
shards: []string{"-20", "20-"},
}
_, err := sel.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{"vals": tupleBV}, false)
require.NoError(t, err)
vc.ExpectLog(t, []string{
`ResolveDestinationsMultiCol user [[INT64(1) VARCHAR("a")] [INT64(4) VARCHAR("b")]] Destinations:DestinationKeyspaceID(166b40b461),DestinationKeyspaceID(d2fd886762)`,
`ExecuteMultiShard user.-20: select 1 from multicol_tbl where (colb, colx, cola) in ::vals {vals: type:TUPLE values:{type:TUPLE value:"\x89\x02\x011\x950\x01a"} values:{type:TUPLE value:"\x89\x02\x014\x950\x01b"}} false false`,
})

vc.Rewind()
_, _ = wrapStreamExecute(sel, vc, map[string]*querypb.BindVariable{"vals": tupleBV}, false)
vc.ExpectLog(t, []string{
`ResolveDestinationsMultiCol user [[INT64(1) VARCHAR("a")] [INT64(4) VARCHAR("b")]] Destinations:DestinationKeyspaceID(166b40b461),DestinationKeyspaceID(d2fd886762)`,
`StreamExecuteMulti select 1 from multicol_tbl where (colb, colx, cola) in ::vals user.-20: {vals: type:TUPLE values:{type:TUPLE value:"\x89\x02\x011\x950\x01a"} values:{type:TUPLE value:"\x89\x02\x014\x950\x01b"}} `,
})
}
12 changes: 12 additions & 0 deletions go/vt/vtgate/evalengine/cached_size.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions go/vt/vtgate/evalengine/expr_bvar.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
switch bvar.Type {
case sqltypes.Tuple:
if bv.Type != sqltypes.Tuple {
return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "query argument '%s' cannot be a tuple", bv.Key)
return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "query argument '%s' must be a tuple (is %s)", bv.Key, bvar.Type)

Check warning on line 68 in go/vt/vtgate/evalengine/expr_bvar.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/expr_bvar.go#L68

Added line #L68 was not covered by tests
}

tuple := make([]eval, 0, len(bvar.Values))
Expand All @@ -80,7 +80,7 @@

default:
if bv.Type == sqltypes.Tuple {
return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "query argument '%s' must be a tuple (is %s)", bv.Key, bvar.Type)
return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "query argument '%s' cannot be a tuple", bv.Key)

Check warning on line 83 in go/vt/vtgate/evalengine/expr_bvar.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/expr_bvar.go#L83

Added line #L83 was not covered by tests
}
typ := bvar.Type
if bv.typed() {
Expand Down
110 changes: 110 additions & 0 deletions go/vt/vtgate/evalengine/expr_tuple_bvar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
Copyright 2023 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 evalengine

import (
"vitess.io/vitess/go/mysql/collations"
"vitess.io/vitess/go/sqltypes"
vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
"vitess.io/vitess/go/vt/vterrors"
)

type (
TupleBindVariable struct {
Key string

Index int
Type sqltypes.Type
Collation collations.ID

// dynamicTypeOffset is set when the type of this bind variable cannot be calculated
// at translation time. Since expressions with dynamic types cannot be compiled ahead of time,
// compilation will be delayed until the expression is first executed with the bind variables
// sent by the user. See: UntypedExpr
dynamicTypeOffset int
}
)

var _ IR = (*TupleBindVariable)(nil)
var _ Expr = (*TupleBindVariable)(nil)

func (bv *TupleBindVariable) IR() IR {
return bv

Check warning on line 46 in go/vt/vtgate/evalengine/expr_tuple_bvar.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/expr_tuple_bvar.go#L45-L46

Added lines #L45 - L46 were not covered by tests
}

func (bv *TupleBindVariable) IsExpr() {}

Check warning on line 49 in go/vt/vtgate/evalengine/expr_tuple_bvar.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/expr_tuple_bvar.go#L49

Added line #L49 was not covered by tests

// eval implements the expression interface
func (bv *TupleBindVariable) eval(env *ExpressionEnv) (eval, error) {
bvar, err := env.lookupBindVar(bv.Key)
if err != nil {
return nil, err
}

Check warning on line 56 in go/vt/vtgate/evalengine/expr_tuple_bvar.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/expr_tuple_bvar.go#L52-L56

Added lines #L52 - L56 were not covered by tests

if bvar.Type != sqltypes.Tuple {
return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "query argument '%s' must be a tuple (is %s)", bv.Key, bvar.Type)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "query argument '%s' must be a tuple (is %s)", bv.Key, bvar.Type)
return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "query argument '%s' must be a tuple (is %s)", bv.Key, bvar.Type.String())

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can add that explicitly. Otherwise, It is not required when a String() method is implemented.

}

Check warning on line 60 in go/vt/vtgate/evalengine/expr_tuple_bvar.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/expr_tuple_bvar.go#L58-L60

Added lines #L58 - L60 were not covered by tests

tuple := make([]eval, 0, len(bvar.Values))
for _, value := range bvar.Values {
if value.Type != sqltypes.Tuple {
return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "result value must be a tuple (is %s)", value.Type)
}
sValue := sqltypes.ProtoToValue(value)
var evalErr error
idx := 0
found := false
loopErr := sValue.ForEachValue(func(val sqltypes.Value) {
idx++
if idx-1 != bv.Index {
return
}
found = true
e, err := valueToEval(val, typedCoercionCollation(val.Type(), collations.CollationForType(val.Type(), bv.Collation)))
if err != nil {
evalErr = err
return
}
tuple = append(tuple, e)

Check warning on line 82 in go/vt/vtgate/evalengine/expr_tuple_bvar.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/expr_tuple_bvar.go#L62-L82

Added lines #L62 - L82 were not covered by tests

})
if loopErr != nil {
return nil, loopErr
}
if evalErr != nil {
return nil, evalErr
}
if !found {
return nil, vterrors.VT13001("value not found in bind variable")
}

Check warning on line 93 in go/vt/vtgate/evalengine/expr_tuple_bvar.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/expr_tuple_bvar.go#L85-L93

Added lines #L85 - L93 were not covered by tests
}
return &evalTuple{t: tuple}, nil

Check warning on line 95 in go/vt/vtgate/evalengine/expr_tuple_bvar.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/expr_tuple_bvar.go#L95

Added line #L95 was not covered by tests
}

// typeof implements the expression interface
func (bv *TupleBindVariable) typeof(env *ExpressionEnv) (ctype, error) {
_, err := env.lookupBindVar(bv.Key)
if err != nil {
return ctype{}, err
}

Check warning on line 103 in go/vt/vtgate/evalengine/expr_tuple_bvar.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/expr_tuple_bvar.go#L99-L103

Added lines #L99 - L103 were not covered by tests

return ctype{Type: sqltypes.Tuple}, nil

Check warning on line 105 in go/vt/vtgate/evalengine/expr_tuple_bvar.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/expr_tuple_bvar.go#L105

Added line #L105 was not covered by tests
}

func (bv *TupleBindVariable) compile(c *compiler) (ctype, error) {
return ctype{}, c.unsupported(bv)

Check warning on line 109 in go/vt/vtgate/evalengine/expr_tuple_bvar.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/expr_tuple_bvar.go#L108-L109

Added lines #L108 - L109 were not covered by tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add the logic for this here? We need this also for the type resolving as well, since we depend on compile for resolving types now as well. See for example also here:

func (u *UntypedExpr) Compile(env *ExpressionEnv) (*CompiledExpr, error) {
typed, err := u.loadTypedExpression(env)
if err != nil {
return nil, err
}
return typed.compile(u.ir, u.collation, u.collationEnv, env.sqlmode)
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a Tuple type bind variable and I am not sure how to compile such.
I checked expr_bvar and it is not recognized there

func (bvar *BindVariable) compile(c *compiler) (ctype, error) {
.
.
.
switch tt := typ.Type; {
.
.
default:
    return ctype{}, vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "Type is not supported: %s", tt)
}
.		

Copy link
Member Author

@harshit-gangal harshit-gangal Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We store something like
(1, 'a'), (2, 'b'), (3, 'c')... in a single bind variable.
and TupleBindVariable will provide single tuple for an index.
If Index is 1 then it returns
('a'), ('b'), ('c'). The output type is still a tuple.

}
12 changes: 12 additions & 0 deletions go/vt/vtgate/evalengine/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,18 @@
}
}

func (bv *TupleBindVariable) Format(buf *sqlparser.TrackedBuffer) {
bv.format(buf)

Check warning on line 156 in go/vt/vtgate/evalengine/format.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/format.go#L155-L156

Added lines #L155 - L156 were not covered by tests
}

func (bv *TupleBindVariable) FormatFast(buf *sqlparser.TrackedBuffer) {
bv.format(buf)

Check warning on line 160 in go/vt/vtgate/evalengine/format.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/format.go#L159-L160

Added lines #L159 - L160 were not covered by tests
}

func (bv *TupleBindVariable) format(buf *sqlparser.TrackedBuffer) {
buf.WriteString(fmt.Sprintf("%s:%d", bv.Key, bv.Index))

Check warning on line 164 in go/vt/vtgate/evalengine/format.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/format.go#L163-L164

Added lines #L163 - L164 were not covered by tests
}

func (c *Column) Format(buf *sqlparser.TrackedBuffer) {
c.format(buf)
}
Expand Down
8 changes: 8 additions & 0 deletions go/vt/vtgate/evalengine/translate_simplify.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
return false
}

func (expr *TupleBindVariable) constant() bool {
return false

Check warning on line 30 in go/vt/vtgate/evalengine/translate_simplify.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/translate_simplify.go#L29-L30

Added lines #L29 - L30 were not covered by tests
}

func (expr *Column) constant() bool {
return false
}
Expand Down Expand Up @@ -55,6 +59,10 @@
return nil
}

func (expr *TupleBindVariable) simplify(_ *ExpressionEnv) error {
return nil

Check warning on line 63 in go/vt/vtgate/evalengine/translate_simplify.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/evalengine/translate_simplify.go#L62-L63

Added lines #L62 - L63 were not covered by tests
}

func (expr *Column) simplify(_ *ExpressionEnv) error {
return nil
}
Expand Down
13 changes: 3 additions & 10 deletions go/vt/vtgate/executor_dml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3065,16 +3065,9 @@ func TestDeleteMulti(t *testing.T) {

testQueryLog(t, executor, logChan, "MarkSavepoint", "SAVEPOINT", "savepoint s1", 8)
testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1)
testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1)
testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1)
testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1)
testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1)
testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1)
testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1)
testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1)
// select `user`.id, `user`.col from `user` - 8 shard
// select 1 from music where music.user_id = 1 and music.col = :user_col - 8 shards
// select Id, `name` from `user` where (`user`.id) in ::dm_vals for update - 8 shards
// delete from `user` where (`user`.id) in ::dm_vals - 8 shards
testQueryLog(t, executor, logChan, "TestExecute", "DELETE", "delete `user` from `user` join music on `user`.col = music.col where music.user_id = 1", 32)
// select Id, `name` from `user` where (`user`.id) in ::dm_vals for update - 1 shard
// delete from `user` where (`user`.id) in ::dm_vals - 1 shard
testQueryLog(t, executor, logChan, "TestExecute", "DELETE", "delete `user` from `user` join music on `user`.col = music.col where music.user_id = 1", 18)
}
69 changes: 69 additions & 0 deletions go/vt/vtgate/executor_select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,75 @@ func TestStreamSelectIN(t *testing.T) {
utils.MustMatch(t, wantQueries, sbclookup.Queries)
}

// TestSelectListArg tests list arg filter with select query
func TestSelectListArg(t *testing.T) {
executor, sbc1, sbc2, _, ctx := createExecutorEnv(t)
session := &vtgatepb.Session{
TargetString: "@primary",
}

tupleBV := &querypb.BindVariable{
Type: querypb.Type_TUPLE,
Values: []*querypb.Value{sqltypes.ValueToProto(sqltypes.TestTuple(sqltypes.NewInt64(1), sqltypes.NewVarChar("a")))},
}
bvMap := map[string]*querypb.BindVariable{"vals": tupleBV}
_, err := executorExec(ctx, executor, session, "select id from user where (id, col) in ::vals", bvMap)
require.NoError(t, err)
wantQueries := []*querypb.BoundQuery{{
Sql: "select id from `user` where (id, col) in ::vals",
BindVariables: bvMap,
}}
utils.MustMatch(t, wantQueries, sbc1.Queries)
assert.Nil(t, sbc2.Queries, "sbc2.Queries: %+v, want nil", sbc2.Queries)

sbc1.Queries = nil
// get c0-e0 sandbox connection.
tbh, err := executor.scatterConn.gateway.hc.GetTabletHealthByAlias(&topodatapb.TabletAlias{
Cell: "aa",
Uid: 7,
})
require.NoError(t, err)
sbc := tbh.Conn.(*sandboxconn.SandboxConn)
sbc.Queries = nil

_, err = executorExec(ctx, executor, session, "select id from multicol_tbl where (cola, colb) in ::vals", bvMap)
require.NoError(t, err)

wantQueries = []*querypb.BoundQuery{{
Sql: "select id from multicol_tbl where (cola, colb) in ::vals",
BindVariables: bvMap,
}}
utils.MustMatch(t, wantQueries, sbc.Queries)
assert.Nil(t, sbc1.Queries, "sbc1.Queries: %+v, want nil", sbc2.Queries)
assert.Nil(t, sbc2.Queries, "sbc2.Queries: %+v, want nil", sbc2.Queries)

tupleBV.Values[0] = sqltypes.ValueToProto(sqltypes.TestTuple(sqltypes.NewInt64(1), sqltypes.NewInt64(42), sqltypes.NewVarChar("a")))
sbc.Queries = nil
_, err = executorExec(ctx, executor, session, "select id from multicol_tbl where (cola, colx, colb) in ::vals", bvMap)
require.NoError(t, err)

wantQueries = []*querypb.BoundQuery{{
Sql: "select id from multicol_tbl where (cola, colx, colb) in ::vals",
BindVariables: bvMap,
}}
utils.MustMatch(t, wantQueries, sbc.Queries)
assert.Nil(t, sbc1.Queries, "sbc1.Queries: %+v, want nil", sbc2.Queries)
assert.Nil(t, sbc2.Queries, "sbc2.Queries: %+v, want nil", sbc2.Queries)

tupleBV.Values[0] = sqltypes.ValueToProto(sqltypes.TestTuple(sqltypes.NewVarChar("a"), sqltypes.NewInt64(42), sqltypes.NewInt64(1)))
sbc.Queries = nil
_, err = executorExec(ctx, executor, session, "select id from multicol_tbl where (colb, colx, cola) in ::vals", bvMap)
require.NoError(t, err)

wantQueries = []*querypb.BoundQuery{{
Sql: "select id from multicol_tbl where (colb, colx, cola) in ::vals",
BindVariables: bvMap,
}}
utils.MustMatch(t, wantQueries, sbc.Queries)
assert.Nil(t, sbc1.Queries, "sbc1.Queries: %+v, want nil", sbc2.Queries)
assert.Nil(t, sbc2.Queries, "sbc2.Queries: %+v, want nil", sbc2.Queries)
}

func createExecutor(ctx context.Context, serv *sandboxTopo, cell string, resolver *Resolver) *Executor {
queryLogger := streamlog.New[*logstats.LogStats]("VTGate", queryLogBufferSize)
plans := DefaultPlanCache()
Expand Down
1 change: 1 addition & 0 deletions go/vt/vtgate/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,7 @@ func TestExecutorShow(t *testing.T) {
buildVarCharRow("TestExecutor", "keyspace_id", "numeric", "", ""),
buildVarCharRow("TestExecutor", "krcol_unique_vdx", "keyrange_lookuper_unique", "", ""),
buildVarCharRow("TestExecutor", "krcol_vdx", "keyrange_lookuper", "", ""),
buildVarCharRow("TestExecutor", "multicol_vdx", "multicol", "column_count=2; column_vindex=xxhash,binary", ""),
buildVarCharRow("TestExecutor", "music_user_map", "lookup_hash_unique", "from=music_id; table=music_user_map; to=user_id", "music"),
buildVarCharRow("TestExecutor", "name_lastname_keyspace_id_map", "lookup", "from=name,lastname; table=name_lastname_keyspace_id_map; to=keyspace_id", "user2"),
buildVarCharRow("TestExecutor", "name_user_map", "lookup_hash", "from=name; table=name_user_map; to=user_id", "user"),
Expand Down
4 changes: 3 additions & 1 deletion go/vt/vtgate/planbuilder/operators/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,9 @@
opcode func(*vindexes.ColumnVindex) engine.Opcode,
) bool {
option.ColsSeen[colLoweredName] = true
option.ValueExprs = append(option.ValueExprs, valueExpr)
if valueExpr != nil {
option.ValueExprs = append(option.ValueExprs, valueExpr)
}

Check warning on line 266 in go/vt/vtgate/planbuilder/operators/route.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/planbuilder/operators/route.go#L264-L266

Added lines #L264 - L266 were not covered by tests
option.Values[indexOfCol] = value
option.Predicates[indexOfCol] = node
option.Ready = len(option.ColsSeen) == len(colVindex.Columns)
Expand Down
Loading
Loading