diff --git a/go/test/endtoend/vtgate/main_test.go b/go/test/endtoend/vtgate/main_test.go index c23de266057..dea6fcc5f3a 100644 --- a/go/test/endtoend/vtgate/main_test.go +++ b/go/test/endtoend/vtgate/main_test.go @@ -100,6 +100,20 @@ create table t5_null_vindex( id bigint not null, idx varchar(50), primary key(id) +) Engine=InnoDB; + +create table t6( + id1 bigint, + id2 varchar(10), + primary key(id1) +) Engine=InnoDB; + +create table t6_id2_idx( + id2 varchar(10), + id1 bigint, + keyspace_id varbinary(50), + primary key(id1), + key(id2) ) Engine=InnoDB;` VSchema = ` @@ -151,6 +165,16 @@ create table t5_null_vindex( "to": "keyspace_id" }, "owner": "t4" + }, + "t6_id2_vdx": { + "type": "consistent_lookup", + "params": { + "table": "t6_id2_idx", + "from": "id2,id1", + "to": "keyspace_id", + "ignore_nulls": "true" + }, + "owner": "t6" } }, "tables": { @@ -233,6 +257,26 @@ create table t5_null_vindex( "name": "unicode_loose_md5" } ] + }, + "t6": { + "column_vindexes": [ + { + "column": "id1", + "name": "hash" + }, + { + "columns": ["id2", "id1"], + "name": "t6_id2_vdx" + } + ] + }, + "t6_id2_idx": { + "column_vindexes": [ + { + "column": "id2", + "name": "xxhash" + } + ] }, "t5_null_vindex": { "column_vindexes": [ diff --git a/go/test/endtoend/vtgate/misc_test.go b/go/test/endtoend/vtgate/misc_test.go index 55cf98328ae..85275e4ed00 100644 --- a/go/test/endtoend/vtgate/misc_test.go +++ b/go/test/endtoend/vtgate/misc_test.go @@ -509,6 +509,28 @@ func TestConsistentLookupUpdate(t *testing.T) { require.Empty(t, qr.Rows) } +func TestSelectNullLookup(t *testing.T) { + ctx := context.Background() + conn, err := mysql.Connect(ctx, &vtParams) + require.NoError(t, err) + defer conn.Close() + + exec(t, conn, "begin") + exec(t, conn, "insert into t6(id1, id2) values(1, 'a'), (2, 'b'), (3, null)") + exec(t, conn, "commit") + + assertMatches(t, conn, "select id1, id2 from t6 order by id1", "[[INT64(1) VARCHAR(\"a\")] [INT64(2) VARCHAR(\"b\")] [INT64(3) NULL]]") + assertIsEmpty(t, conn, "select id1, id2 from t6 where id2 = null") + assertMatches(t, conn, "select id1, id2 from t6 where id2 is null", "[[INT64(3) NULL]]") + assertMatches(t, conn, "select id1, id2 from t6 where id2 is not null order by id1", "[[INT64(1) VARCHAR(\"a\")] [INT64(2) VARCHAR(\"b\")]]") + assertIsEmpty(t, conn, "select id1, id2 from t6 where id1 IN (null)") + assertMatches(t, conn, "select id1, id2 from t6 where id1 IN (1,2,null) order by id1", "[[INT64(1) VARCHAR(\"a\")] [INT64(2) VARCHAR(\"b\")]]") + assertIsEmpty(t, conn, "select id1, id2 from t6 where id1 NOT IN (1,null) order by id1") + assertMatches(t, conn, "select id1, id2 from t6 where id1 NOT IN (1,3)", "[[INT64(2) VARCHAR(\"b\")]]") + + exec(t, conn, "delete from t6") +} + func TestSelectNull(t *testing.T) { ctx := context.Background() conn, err := mysql.Connect(ctx, &vtParams) diff --git a/go/vt/vtgate/planbuilder/route_option.go b/go/vt/vtgate/planbuilder/route_option.go index 4543f065f8a..4710cde0bfd 100644 --- a/go/vt/vtgate/planbuilder/route_option.go +++ b/go/vt/vtgate/planbuilder/route_option.go @@ -301,6 +301,7 @@ func (ro *routeOption) computeISPlan(pb *primitiveBuilder, comparison *sqlparser } vindex = ro.FindVindex(pb, comparison.Expr) + // fallback to scatter gather if there is no vindex if vindex == nil { return engine.SelectScatter, nil, nil } diff --git a/go/vt/vtgate/vindexes/consistent_lookup.go b/go/vt/vtgate/vindexes/consistent_lookup.go index ca2bf92480e..14e5f84cc9a 100644 --- a/go/vt/vtgate/vindexes/consistent_lookup.go +++ b/go/vt/vtgate/vindexes/consistent_lookup.go @@ -91,6 +91,14 @@ func (lu *ConsistentLookup) Map(vcursor VCursor, ids []sqltypes.Value) ([]key.De return out, nil } + // if ignore_nulls is set and the query is about single null value, then fallback to all shards + if len(ids) == 1 && ids[0].IsNull() && lu.lkp.IgnoreNulls { + for range ids { + out = append(out, key.DestinationKeyRange{KeyRange: &topodatapb.KeyRange{}}) + } + return out, nil + } + results, err := lu.lkp.Lookup(vcursor, ids) if err != nil { return nil, err diff --git a/go/vt/vtgate/vindexes/lookup.go b/go/vt/vtgate/vindexes/lookup.go index 5474cc17887..6b2b6e0c066 100644 --- a/go/vt/vtgate/vindexes/lookup.go +++ b/go/vt/vtgate/vindexes/lookup.go @@ -76,6 +76,14 @@ func (ln *LookupNonUnique) Map(vcursor VCursor, ids []sqltypes.Value) ([]key.Des return out, nil } + // if ignore_nulls is set and the query is about single null value, then fallback to all shards + if len(ids) == 1 && ids[0].IsNull() && ln.lkp.IgnoreNulls { + for range ids { + out = append(out, key.DestinationKeyRange{KeyRange: &topodatapb.KeyRange{}}) + } + return out, nil + } + results, err := ln.lkp.Lookup(vcursor, ids) if err != nil { return nil, err diff --git a/go/vt/vtgate/vindexes/lookup_hash.go b/go/vt/vtgate/vindexes/lookup_hash.go index 5787308e276..4eeb510862f 100644 --- a/go/vt/vtgate/vindexes/lookup_hash.go +++ b/go/vt/vtgate/vindexes/lookup_hash.go @@ -110,6 +110,14 @@ func (lh *LookupHash) Map(vcursor VCursor, ids []sqltypes.Value) ([]key.Destinat return out, nil } + // if ignore_nulls is set and the query is about single null value, then fallback to all shards + if len(ids) == 1 && ids[0].IsNull() && lh.lkp.IgnoreNulls { + for range ids { + out = append(out, key.DestinationKeyRange{KeyRange: &topodatapb.KeyRange{}}) + } + return out, nil + } + results, err := lh.lkp.Lookup(vcursor, ids) if err != nil { return nil, err diff --git a/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go index 000b6d21608..f0fc787d8b0 100644 --- a/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go +++ b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go @@ -111,6 +111,14 @@ func (lh *LookupUnicodeLooseMD5Hash) Map(vcursor VCursor, ids []sqltypes.Value) return out, nil } + // if ignore_nulls is set and the query is about single null value, then fallback to all shards + if len(ids) == 1 && ids[0].IsNull() && lh.lkp.IgnoreNulls { + for range ids { + out = append(out, key.DestinationKeyRange{KeyRange: &topodatapb.KeyRange{}}) + } + return out, nil + } + ids, err := convertIds(ids) if err != nil { return nil, err