From cf40bc849c2bd11ed294decd63aecc29bd7a3feb Mon Sep 17 00:00:00 2001 From: PlanetScale Actions Bot <60239337+planetscale-actions-bot@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:14:06 -0400 Subject: [PATCH] backport of 2647 (#2670) --- .../planbuilder/operators/sharded_routing.go | 4 + go/vt/vtgate/planbuilder/plan_test.go | 193 -------------- go/vt/vtgate/planbuilder/plan_test_vindex.go | 222 ++++++++++++++++ .../planbuilder/testdata/select_cases.json | 251 ++++++++++++++++++ .../planbuilder/testdata/vschemas/schema.json | 45 ++++ go/vt/vtgate/vindexes/vschema.go | 26 +- 6 files changed, 541 insertions(+), 200 deletions(-) create mode 100644 go/vt/vtgate/planbuilder/plan_test_vindex.go diff --git a/go/vt/vtgate/planbuilder/operators/sharded_routing.go b/go/vt/vtgate/planbuilder/operators/sharded_routing.go index e565684507e..c0c9545f080 100644 --- a/go/vt/vtgate/planbuilder/operators/sharded_routing.go +++ b/go/vt/vtgate/planbuilder/operators/sharded_routing.go @@ -79,6 +79,10 @@ func newShardedRouting(vtable *vindexes.Table, id semantics.TableSet) Routing { } for _, columnVindex := range vtable.ColumnVindexes { + // ignore any backfilling vindexes from vindex selection. + if columnVindex.IsBackfilling() { + continue + } routing.VindexPreds = append(routing.VindexPreds, &VindexPlusPredicates{ColVindex: columnVindex, TableID: id}) } return routing diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go index 3e9c3a0006f..1accbf89c2b 100644 --- a/go/vt/vtgate/planbuilder/plan_test.go +++ b/go/vt/vtgate/planbuilder/plan_test.go @@ -52,199 +52,6 @@ import ( "vitess.io/vitess/go/vt/vtgate/vindexes" ) -// hashIndex is a functional, unique Vindex. -type hashIndex struct{ name string } - -func (v *hashIndex) String() string { return v.name } - -func (*hashIndex) Cost() int { return 1 } - -func (*hashIndex) IsUnique() bool { return true } - -func (*hashIndex) NeedsVCursor() bool { return false } - -func (*hashIndex) Verify(context.Context, vindexes.VCursor, []sqltypes.Value, [][]byte) ([]bool, error) { - return []bool{}, nil -} - -func (*hashIndex) Map(ctx context.Context, vcursor vindexes.VCursor, ids []sqltypes.Value) ([]key.Destination, error) { - return nil, nil -} - -func newHashIndex(name string, _ map[string]string) (vindexes.Vindex, error) { - return &hashIndex{name: name}, nil -} - -// lookupIndex is a unique Vindex, and satisfies Lookup. -type lookupIndex struct{ name string } - -func (v *lookupIndex) String() string { return v.name } - -func (*lookupIndex) Cost() int { return 2 } - -func (*lookupIndex) IsUnique() bool { return true } - -func (*lookupIndex) NeedsVCursor() bool { return false } - -func (*lookupIndex) Verify(context.Context, vindexes.VCursor, []sqltypes.Value, [][]byte) ([]bool, error) { - return []bool{}, nil -} - -func (*lookupIndex) Map(ctx context.Context, vcursor vindexes.VCursor, ids []sqltypes.Value) ([]key.Destination, error) { - return nil, nil -} - -func (*lookupIndex) Create(context.Context, vindexes.VCursor, [][]sqltypes.Value, [][]byte, bool) error { - return nil -} - -func (*lookupIndex) Delete(context.Context, vindexes.VCursor, [][]sqltypes.Value, []byte) error { - return nil -} - -func (*lookupIndex) Update(context.Context, vindexes.VCursor, []sqltypes.Value, []byte, []sqltypes.Value) error { - return nil -} - -func newLookupIndex(name string, _ map[string]string) (vindexes.Vindex, error) { - return &lookupIndex{name: name}, nil -} - -var _ vindexes.Lookup = (*lookupIndex)(nil) - -// nameLkpIndex satisfies Lookup, NonUnique. -type nameLkpIndex struct{ name string } - -func (v *nameLkpIndex) String() string { return v.name } - -func (*nameLkpIndex) Cost() int { return 3 } - -func (*nameLkpIndex) IsUnique() bool { return false } - -func (*nameLkpIndex) NeedsVCursor() bool { return false } - -func (*nameLkpIndex) AllowBatch() bool { return true } - -func (*nameLkpIndex) AutoCommitEnabled() bool { return false } - -func (*nameLkpIndex) GetCommitOrder() vtgatepb.CommitOrder { return vtgatepb.CommitOrder_NORMAL } - -func (*nameLkpIndex) Verify(context.Context, vindexes.VCursor, []sqltypes.Value, [][]byte) ([]bool, error) { - return []bool{}, nil -} - -func (*nameLkpIndex) Map(ctx context.Context, vcursor vindexes.VCursor, ids []sqltypes.Value) ([]key.Destination, error) { - return nil, nil -} - -func (*nameLkpIndex) Create(context.Context, vindexes.VCursor, [][]sqltypes.Value, [][]byte, bool) error { - return nil -} - -func (*nameLkpIndex) Delete(context.Context, vindexes.VCursor, [][]sqltypes.Value, []byte) error { - return nil -} - -func (*nameLkpIndex) Update(context.Context, vindexes.VCursor, []sqltypes.Value, []byte, []sqltypes.Value) error { - return nil -} - -func (v *nameLkpIndex) Query() (string, []string) { - return "select name, keyspace_id from name_user_vdx where name in ::name", []string{"name"} -} - -func (*nameLkpIndex) MapResult([]sqltypes.Value, []*sqltypes.Result) ([]key.Destination, error) { - return nil, nil -} - -func newNameLkpIndex(name string, _ map[string]string) (vindexes.Vindex, error) { - return &nameLkpIndex{name: name}, nil -} - -var _ vindexes.Vindex = (*nameLkpIndex)(nil) - -var _ vindexes.Lookup = (*nameLkpIndex)(nil) - -var _ vindexes.LookupPlanable = (*nameLkpIndex)(nil) - -// costlyIndex satisfies Lookup, NonUnique. -type costlyIndex struct{ name string } - -func (v *costlyIndex) String() string { return v.name } - -func (*costlyIndex) Cost() int { return 10 } - -func (*costlyIndex) IsUnique() bool { return false } - -func (*costlyIndex) NeedsVCursor() bool { return false } - -func (*costlyIndex) Verify(context.Context, vindexes.VCursor, []sqltypes.Value, [][]byte) ([]bool, error) { - return []bool{}, nil -} - -func (*costlyIndex) Map(ctx context.Context, vcursor vindexes.VCursor, ids []sqltypes.Value) ([]key.Destination, error) { - return nil, nil -} - -func (*costlyIndex) Create(context.Context, vindexes.VCursor, [][]sqltypes.Value, [][]byte, bool) error { - return nil -} - -func (*costlyIndex) Delete(context.Context, vindexes.VCursor, [][]sqltypes.Value, []byte) error { - return nil -} - -func (*costlyIndex) Update(context.Context, vindexes.VCursor, []sqltypes.Value, []byte, []sqltypes.Value) error { - return nil -} - -func newCostlyIndex(name string, _ map[string]string) (vindexes.Vindex, error) { - return &costlyIndex{name: name}, nil -} - -var _ vindexes.Vindex = (*costlyIndex)(nil) - -var _ vindexes.Lookup = (*costlyIndex)(nil) - -// multiColIndex satisfies multi column vindex. -type multiColIndex struct { - name string -} - -func newMultiColIndex(name string, _ map[string]string) (vindexes.Vindex, error) { - return &multiColIndex{name: name}, nil -} - -var _ vindexes.MultiColumn = (*multiColIndex)(nil) - -func (m *multiColIndex) String() string { return m.name } - -func (m *multiColIndex) Cost() int { return 1 } - -func (m *multiColIndex) IsUnique() bool { return true } - -func (m *multiColIndex) NeedsVCursor() bool { return false } - -func (m *multiColIndex) Map(ctx context.Context, vcursor vindexes.VCursor, rowsColValues [][]sqltypes.Value) ([]key.Destination, error) { - return nil, nil -} - -func (m *multiColIndex) Verify(ctx context.Context, vcursor vindexes.VCursor, rowsColValues [][]sqltypes.Value, ksids [][]byte) ([]bool, error) { - return []bool{}, nil -} - -func (m *multiColIndex) PartialVindex() bool { - return true -} - -func init() { - vindexes.Register("hash_test", newHashIndex) - vindexes.Register("lookup_test", newLookupIndex) - vindexes.Register("name_lkp_test", newNameLkpIndex) - vindexes.Register("costly", newCostlyIndex) - vindexes.Register("multiCol_test", newMultiColIndex) -} - func makeTestOutput(t *testing.T) string { testOutputTempDir := utils.MakeTestOutput(t, "testdata", "plan_test") diff --git a/go/vt/vtgate/planbuilder/plan_test_vindex.go b/go/vt/vtgate/planbuilder/plan_test_vindex.go new file mode 100644 index 00000000000..432ef7b8479 --- /dev/null +++ b/go/vt/vtgate/planbuilder/plan_test_vindex.go @@ -0,0 +1,222 @@ +/* +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 planbuilder + +import ( + "context" + "strconv" + + "vitess.io/vitess/go/sqltypes" + "vitess.io/vitess/go/vt/key" + vtgatepb "vitess.io/vitess/go/vt/proto/vtgate" + "vitess.io/vitess/go/vt/vtgate/vindexes" +) + +// hashIndex is a functional, unique Vindex. +type hashIndex struct{ name string } + +func (v *hashIndex) String() string { return v.name } +func (*hashIndex) Cost() int { return 1 } +func (*hashIndex) IsUnique() bool { return true } +func (*hashIndex) NeedsVCursor() bool { return false } +func (*hashIndex) Verify(context.Context, vindexes.VCursor, []sqltypes.Value, [][]byte) ([]bool, error) { + return []bool{}, nil +} +func (*hashIndex) Map(ctx context.Context, vcursor vindexes.VCursor, ids []sqltypes.Value) ([]key.Destination, error) { + return nil, nil +} +func newHashIndex(name string, _ map[string]string) (vindexes.Vindex, error) { + return &hashIndex{name: name}, nil +} + +// lookupIndex is a unique Vindex, and satisfies Lookup. +type lookupIndex struct{ name string } + +func (v *lookupIndex) String() string { return v.name } +func (*lookupIndex) Cost() int { return 2 } +func (*lookupIndex) IsUnique() bool { return true } +func (*lookupIndex) NeedsVCursor() bool { return false } +func (*lookupIndex) Verify(context.Context, vindexes.VCursor, []sqltypes.Value, [][]byte) ([]bool, error) { + return []bool{}, nil +} +func (*lookupIndex) Map(ctx context.Context, vcursor vindexes.VCursor, ids []sqltypes.Value) ([]key.Destination, error) { + return nil, nil +} +func (*lookupIndex) Create(context.Context, vindexes.VCursor, [][]sqltypes.Value, [][]byte, bool) error { + return nil +} +func (*lookupIndex) Delete(context.Context, vindexes.VCursor, [][]sqltypes.Value, []byte) error { + return nil +} +func (*lookupIndex) Update(context.Context, vindexes.VCursor, []sqltypes.Value, []byte, []sqltypes.Value) error { + return nil +} +func newLookupIndex(name string, _ map[string]string) (vindexes.Vindex, error) { + return &lookupIndex{name: name}, nil +} + +var _ vindexes.Lookup = (*lookupIndex)(nil) + +// nameLkpIndex satisfies Lookup, NonUnique. +type nameLkpIndex struct{ name string } + +func (v *nameLkpIndex) String() string { return v.name } +func (*nameLkpIndex) Cost() int { return 3 } +func (*nameLkpIndex) IsUnique() bool { return false } +func (*nameLkpIndex) NeedsVCursor() bool { return false } +func (*nameLkpIndex) AllowBatch() bool { return true } +func (*nameLkpIndex) AutoCommitEnabled() bool { return false } +func (*nameLkpIndex) GetCommitOrder() vtgatepb.CommitOrder { return vtgatepb.CommitOrder_NORMAL } +func (*nameLkpIndex) Verify(context.Context, vindexes.VCursor, []sqltypes.Value, [][]byte) ([]bool, error) { + return []bool{}, nil +} +func (*nameLkpIndex) Map(ctx context.Context, vcursor vindexes.VCursor, ids []sqltypes.Value) ([]key.Destination, error) { + return nil, nil +} +func (*nameLkpIndex) Create(context.Context, vindexes.VCursor, [][]sqltypes.Value, [][]byte, bool) error { + return nil +} +func (*nameLkpIndex) Delete(context.Context, vindexes.VCursor, [][]sqltypes.Value, []byte) error { + return nil +} +func (*nameLkpIndex) Update(context.Context, vindexes.VCursor, []sqltypes.Value, []byte, []sqltypes.Value) error { + return nil +} +func (*nameLkpIndex) Query() (string, []string) { + return "select name, keyspace_id from name_user_vdx where name in ::name", []string{"name"} +} +func (*nameLkpIndex) MapResult([]sqltypes.Value, []*sqltypes.Result) ([]key.Destination, error) { + return nil, nil +} +func newNameLkpIndex(name string, _ map[string]string) (vindexes.Vindex, error) { + return &nameLkpIndex{name: name}, nil +} + +var _ vindexes.Vindex = (*nameLkpIndex)(nil) +var _ vindexes.Lookup = (*nameLkpIndex)(nil) +var _ vindexes.LookupPlanable = (*nameLkpIndex)(nil) + +// costlyIndex satisfies Lookup, NonUnique. +type costlyIndex struct{ name string } + +func (v *costlyIndex) String() string { return v.name } +func (*costlyIndex) Cost() int { return 10 } +func (*costlyIndex) IsUnique() bool { return false } +func (*costlyIndex) NeedsVCursor() bool { return false } +func (*costlyIndex) Verify(context.Context, vindexes.VCursor, []sqltypes.Value, [][]byte) ([]bool, error) { + return []bool{}, nil +} +func (*costlyIndex) Map(ctx context.Context, vcursor vindexes.VCursor, ids []sqltypes.Value) ([]key.Destination, error) { + return nil, nil +} +func (*costlyIndex) Create(context.Context, vindexes.VCursor, [][]sqltypes.Value, [][]byte, bool) error { + return nil +} +func (*costlyIndex) Delete(context.Context, vindexes.VCursor, [][]sqltypes.Value, []byte) error { + return nil +} +func (*costlyIndex) Update(context.Context, vindexes.VCursor, []sqltypes.Value, []byte, []sqltypes.Value) error { + return nil +} +func newCostlyIndex(name string, _ map[string]string) (vindexes.Vindex, error) { + return &costlyIndex{name: name}, nil +} + +var _ vindexes.Vindex = (*costlyIndex)(nil) +var _ vindexes.Lookup = (*costlyIndex)(nil) + +// multiColIndex satisfies multi column vindex. +type multiColIndex struct{ name string } + +func (m *multiColIndex) String() string { return m.name } +func (*multiColIndex) Cost() int { return 1 } +func (*multiColIndex) IsUnique() bool { return true } +func (*multiColIndex) NeedsVCursor() bool { return false } +func (*multiColIndex) Map(ctx context.Context, vcursor vindexes.VCursor, rowsColValues [][]sqltypes.Value) ([]key.Destination, error) { + return nil, nil +} +func (*multiColIndex) Verify(ctx context.Context, vcursor vindexes.VCursor, rowsColValues [][]sqltypes.Value, ksids [][]byte) ([]bool, error) { + return []bool{}, nil +} +func (*multiColIndex) PartialVindex() bool { return true } +func newMultiColIndex(name string, _ map[string]string) (vindexes.Vindex, error) { + return &multiColIndex{name: name}, nil +} + +var _ vindexes.MultiColumn = (*multiColIndex)(nil) + +// unqLkpVdxBackfill satisfies Lookup, Unique. +type unqLkpVdxBackfill struct { + name string + inBackfill bool + cost int +} + +func (u *unqLkpVdxBackfill) String() string { return u.name } +func (u *unqLkpVdxBackfill) Cost() int { return u.cost } +func (*unqLkpVdxBackfill) IsUnique() bool { return false } +func (*unqLkpVdxBackfill) NeedsVCursor() bool { return false } +func (*unqLkpVdxBackfill) AllowBatch() bool { return true } +func (*unqLkpVdxBackfill) AutoCommitEnabled() bool { return false } +func (*unqLkpVdxBackfill) GetCommitOrder() vtgatepb.CommitOrder { return vtgatepb.CommitOrder_NORMAL } +func (*unqLkpVdxBackfill) Verify(context.Context, vindexes.VCursor, []sqltypes.Value, [][]byte) ([]bool, error) { + return []bool{}, nil +} +func (*unqLkpVdxBackfill) Map(ctx context.Context, vcursor vindexes.VCursor, ids []sqltypes.Value) ([]key.Destination, error) { + return nil, nil +} +func (*unqLkpVdxBackfill) Create(context.Context, vindexes.VCursor, [][]sqltypes.Value, [][]byte, bool) error { + return nil +} +func (*unqLkpVdxBackfill) Delete(context.Context, vindexes.VCursor, [][]sqltypes.Value, []byte) error { + return nil +} +func (*unqLkpVdxBackfill) Update(context.Context, vindexes.VCursor, []sqltypes.Value, []byte, []sqltypes.Value) error { + return nil +} +func (*unqLkpVdxBackfill) Query() (string, []string) { + return "select unq_key, keyspace_id from unq_lkp_idx where unq_key in ::unq_key", []string{"unq_key"} +} +func (*unqLkpVdxBackfill) MapResult([]sqltypes.Value, []*sqltypes.Result) ([]key.Destination, error) { + return nil, nil +} +func (u *unqLkpVdxBackfill) IsBackfilling() bool { return u.inBackfill } + +func newUnqLkpVdxBackfill(name string, m map[string]string) (vindexes.Vindex, error) { + vdx := &unqLkpVdxBackfill{name: name} + if val, ok := m["write_only"]; ok { + vdx.inBackfill = val == "true" + } + if val, ok := m["cost"]; ok { + vdx.cost, _ = strconv.Atoi(val) + } + return vdx, nil +} + +var _ vindexes.Vindex = (*unqLkpVdxBackfill)(nil) +var _ vindexes.Lookup = (*unqLkpVdxBackfill)(nil) +var _ vindexes.LookupPlanable = (*unqLkpVdxBackfill)(nil) +var _ vindexes.LookupBackfill = (*unqLkpVdxBackfill)(nil) + +func init() { + vindexes.Register("hash_test", newHashIndex) + vindexes.Register("lookup_test", newLookupIndex) + vindexes.Register("name_lkp_test", newNameLkpIndex) + vindexes.Register("costly", newCostlyIndex) + vindexes.Register("multiCol_test", newMultiColIndex) + vindexes.Register("unq_lkp_test", newUnqLkpVdxBackfill) +} diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases.json b/go/vt/vtgate/planbuilder/testdata/select_cases.json index 08227bc5a15..b0cef24cce9 100644 --- a/go/vt/vtgate/planbuilder/testdata/select_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/select_cases.json @@ -8160,5 +8160,256 @@ "user.user_metadata" ] } + }, + { + "comment": "pick email as vindex lookup", + "query": "select * from customer where email = 'a@mail.com'", + "v3-plan": { + "QueryType": "SELECT", + "Original": "select * from customer where email = 'a@mail.com'", + "Instructions": { + "OperatorType": "Route", + "Variant": "Equal", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select * from customer where 1 != 1", + "Query": "select * from customer where email = 'a@mail.com'", + "Table": "customer", + "Values": [ + "VARCHAR(\"a@mail.com\")" + ], + "Vindex": "unq_lkp_vdx" + } + }, + "gen4-plan": { + "QueryType": "SELECT", + "Original": "select * from customer where email = 'a@mail.com'", + "Instructions": { + "OperatorType": "VindexLookup", + "Variant": "Equal", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Values": [ + "VARCHAR(\"a@mail.com\")" + ], + "Vindex": "unq_lkp_vdx", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select unq_key, keyspace_id from unq_lkp_idx where 1 != 1", + "Query": "select unq_key, keyspace_id from unq_lkp_idx where unq_key in ::__vals", + "Table": "unq_lkp_idx", + "Values": [ + "::unq_key" + ], + "Vindex": "shard_index" + }, + { + "OperatorType": "Route", + "Variant": "ByDestination", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select * from customer where 1 != 1", + "Query": "select * from customer where email = 'a@mail.com'", + "Table": "customer" + } + ] + }, + "TablesUsed": [ + "user.customer" + ] + } + }, + { + "comment": "phone is in backfill vindex - not selected for vindex lookup", + "query": "select * from customer where phone = 123456", + "v3-plan": { + "QueryType": "SELECT", + "Original": "select * from customer where phone = 123456", + "Instructions": { + "OperatorType": "Route", + "Variant": "Equal", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select * from customer where 1 != 1", + "Query": "select * from customer where phone = 123456", + "Table": "customer", + "Values": [ + "INT64(123456)" + ], + "Vindex": "unq_lkp_bf_vdx" + } + }, + "gen4-plan": { + "QueryType": "SELECT", + "Original": "select * from customer where phone = 123456", + "Instructions": { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select * from customer where 1 != 1", + "Query": "select * from customer where phone = 123456", + "Table": "customer" + }, + "TablesUsed": [ + "user.customer" + ] + } + }, + { + "comment": "email vindex is costly than phone vindex - but phone vindex is backfiling hence ignored", + "query": "select * from customer where email = 'a@mail.com' and phone = 123456", + "v3-plan": { + "QueryType": "SELECT", + "Original": "select * from customer where email = 'a@mail.com' and phone = 123456", + "Instructions": { + "OperatorType": "Route", + "Variant": "Equal", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select * from customer where 1 != 1", + "Query": "select * from customer where email = 'a@mail.com' and phone = 123456", + "Table": "customer", + "Values": [ + "INT64(123456)" + ], + "Vindex": "unq_lkp_bf_vdx" + } + }, + "gen4-plan": { + "QueryType": "SELECT", + "Original": "select * from customer where email = 'a@mail.com' and phone = 123456", + "Instructions": { + "OperatorType": "VindexLookup", + "Variant": "Equal", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Values": [ + "VARCHAR(\"a@mail.com\")" + ], + "Vindex": "unq_lkp_vdx", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select unq_key, keyspace_id from unq_lkp_idx where 1 != 1", + "Query": "select unq_key, keyspace_id from unq_lkp_idx where unq_key in ::__vals", + "Table": "unq_lkp_idx", + "Values": [ + "::unq_key" + ], + "Vindex": "shard_index" + }, + { + "OperatorType": "Route", + "Variant": "ByDestination", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select * from customer where 1 != 1", + "Query": "select * from customer where email = 'a@mail.com' and phone = 123456", + "Table": "customer" + } + ] + }, + "TablesUsed": [ + "user.customer" + ] + } + }, + { + "comment": "predicate order changed: email vindex is costly than phone vindex - but phone vindex is backfiling hence ignored", + "query": "select * from customer where phone = 123456 and email = 'a@mail.com'", + "v3-plan": { + "QueryType": "SELECT", + "Original": "select * from customer where phone = 123456 and email = 'a@mail.com'", + "Instructions": { + "OperatorType": "Route", + "Variant": "Equal", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select * from customer where 1 != 1", + "Query": "select * from customer where phone = 123456 and email = 'a@mail.com'", + "Table": "customer", + "Values": [ + "INT64(123456)" + ], + "Vindex": "unq_lkp_bf_vdx" + } + }, + "gen4-plan": { + "QueryType": "SELECT", + "Original": "select * from customer where phone = 123456 and email = 'a@mail.com'", + "Instructions": { + "OperatorType": "VindexLookup", + "Variant": "Equal", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Values": [ + "VARCHAR(\"a@mail.com\")" + ], + "Vindex": "unq_lkp_vdx", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select unq_key, keyspace_id from unq_lkp_idx where 1 != 1", + "Query": "select unq_key, keyspace_id from unq_lkp_idx where unq_key in ::__vals", + "Table": "unq_lkp_idx", + "Values": [ + "::unq_key" + ], + "Vindex": "shard_index" + }, + { + "OperatorType": "Route", + "Variant": "ByDestination", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select * from customer where 1 != 1", + "Query": "select * from customer where phone = 123456 and email = 'a@mail.com'", + "Table": "customer" + } + ] + }, + "TablesUsed": [ + "user.customer" + ] + } } ] diff --git a/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json b/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json index 6c771070f1b..9f9ea97200f 100644 --- a/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json +++ b/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json @@ -145,6 +145,27 @@ }, "shard_index": { "type": "xxhash" + }, + "unq_lkp_bf_vdx": { + "type": "unq_lkp_test", + "owner": "customer", + "params": { + "table": "unq_lkp_idx", + "from": " ", + "to": "keyspace_id", + "cost": "100", + "write_only": "true" + } + }, + "unq_lkp_vdx": { + "type": "unq_lkp_test", + "owner": "customer", + "params": { + "table": "unq_lkp_idx", + "from": "unq_key", + "to": "keyspace_id", + "cost": "300" + } } }, "tables": { @@ -437,6 +458,30 @@ "name": "shard_index" } ] + }, + "customer": { + "column_vindexes": [ + { + "column": "id", + "name": "shard_index" + }, + { + "column": "email", + "name": "unq_lkp_vdx" + }, + { + "column": "phone", + "name": "unq_lkp_bf_vdx" + } + ] + }, + "unq_lkp_idx": { + "column_vindexes": [ + { + "column": "unq_key", + "name": "shard_index" + } + ] } } }, diff --git a/go/vt/vtgate/vindexes/vschema.go b/go/vt/vtgate/vindexes/vschema.go index 28ac889ece1..b62b5ce46f7 100644 --- a/go/vt/vtgate/vindexes/vschema.go +++ b/go/vt/vtgate/vindexes/vschema.go @@ -129,6 +129,7 @@ type ColumnVindex struct { isUnique bool cost int partial bool + backfill bool } // IsUnique is used to tell whether the ColumnVindex @@ -149,6 +150,11 @@ func (c *ColumnVindex) IsPartialVindex() bool { return c.partial } +// IsBackfilling returns true if the vindex is in the process of backfilling the rows. +func (c *ColumnVindex) IsBackfilling() bool { + return c.backfill +} + // Column describes a column. type Column struct { Name sqlparser.IdentifierCI `json:"name"` @@ -616,6 +622,10 @@ func buildTables(ks *vschemapb.Keyspace, vschema *VSchema, ksvschema *KeyspaceSc columns = append(columns, sqlparser.NewIdentifierCI(indCol)) } } + backfill := false + if lkpBackfill, ok := vindex.(LookupBackfill); ok { + backfill = lkpBackfill.IsBackfilling() + } columnVindex := &ColumnVindex{ Columns: columns, Type: vindexInfo.Type, @@ -624,6 +634,7 @@ func buildTables(ks *vschemapb.Keyspace, vschema *VSchema, ksvschema *KeyspaceSc Vindex: vindex, isUnique: vindex.IsUnique(), cost: vindex.Cost(), + backfill: backfill, } if i == 0 { // Perform Primary vindex check. @@ -676,13 +687,14 @@ func buildTables(ks *vschemapb.Keyspace, vschema *VSchema, ksvschema *KeyspaceSc columnSubset := columns[:i] cost++ columnVindex = &ColumnVindex{ - Columns: columnSubset, - Type: vindexInfo.Type, - Name: ind.Name, - Owned: owned, - Vindex: vindex, - cost: cost, - partial: true, + Columns: columnSubset, + Type: vindexInfo.Type, + Name: ind.Name, + Owned: owned, + Vindex: vindex, + cost: cost, + partial: true, + backfill: backfill, } t.ColumnVindexes = append(t.ColumnVindexes, columnVindex) }