diff --git a/go/test/endtoend/vtgate/lookup_test.go b/go/test/endtoend/vtgate/lookup_test.go index 444b7fb150b..c34ac6de527 100644 --- a/go/test/endtoend/vtgate/lookup_test.go +++ b/go/test/endtoend/vtgate/lookup_test.go @@ -21,9 +21,9 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" + "github.com/google/go-cmp/cmp" - "vitess.io/vitess/go/test/utils" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -516,19 +516,33 @@ func TestSelectNull(t *testing.T) { exec(t, conn, "begin") exec(t, conn, "insert into t5_null_vindex(id, idx) values(1, 'a'), (2, 'b'), (3, null)") exec(t, conn, "commit") - qr := exec(t, conn, "select id, idx from t5_null_vindex order by id") - utils.MustMatch(t, fmt.Sprintf("%v", qr.Rows), "[[INT64(1) VARCHAR(\"a\")] [INT64(2) VARCHAR(\"b\")] [INT64(3) NULL]]", "") - qr = exec(t, conn, "select id, idx from t5_null_vindex where idx = null") - require.Empty(t, qr.Rows) + assertMatches(t, conn, "select id, idx from t5_null_vindex order by id", "[[INT64(1) VARCHAR(\"a\")] [INT64(2) VARCHAR(\"b\")] [INT64(3) NULL]]") + assertIsEmpty(t, conn, "select id, idx from t5_null_vindex where idx = null") + assertMatches(t, conn, "select id, idx from t5_null_vindex where idx is null", "[[INT64(3) NULL]]") + assertMatches(t, conn, "select id, idx from t5_null_vindex where idx is not null order by id", "[[INT64(1) VARCHAR(\"a\")] [INT64(2) VARCHAR(\"b\")]]") + assertIsEmpty(t, conn, "select id, idx from t5_null_vindex where id IN (null)") + assertMatches(t, conn, "select id, idx from t5_null_vindex where id IN (1,2,null) order by id", "[[INT64(1) VARCHAR(\"a\")] [INT64(2) VARCHAR(\"b\")]]") + assertIsEmpty(t, conn, "select id, idx from t5_null_vindex where id NOT IN (1,null) order by id") + assertMatches(t, conn, "select id, idx from t5_null_vindex where id NOT IN (1,3)", "[[INT64(2) VARCHAR(\"b\")]]") - qr = exec(t, conn, "select id, idx from t5_null_vindex where idx is null") - utils.MustMatch(t, fmt.Sprintf("%v", qr.Rows), "[[INT64(3) NULL]]", "") + exec(t, conn, "delete from t5_null_vindex") +} - qr = exec(t, conn, "select id, idx from t5_null_vindex where idx is not null order by id") - utils.MustMatch(t, fmt.Sprintf("%v", qr.Rows), "[[INT64(1) VARCHAR(\"a\")] [INT64(2) VARCHAR(\"b\")]]", "") +func assertMatches(t *testing.T, conn *mysql.Conn, query, expected string) { + t.Helper() + qr := exec(t, conn, query) + got := fmt.Sprintf("%v", qr.Rows) + diff := cmp.Diff(expected, got) + if diff != "" { + t.Errorf("Query: %s (-want +got):\n%s", query, diff) + } +} - exec(t, conn, "delete from t5_null_vindex") +func assertIsEmpty(t *testing.T, conn *mysql.Conn, query string) { + t.Helper() + qr := exec(t, conn, query) + assert.Empty(t, qr.Rows) } func exec(t *testing.T, conn *mysql.Conn, query string) *sqltypes.Result { diff --git a/go/vt/vtgate/planbuilder/route_option.go b/go/vt/vtgate/planbuilder/route_option.go index c38c163231e..4543f065f8a 100644 --- a/go/vt/vtgate/planbuilder/route_option.go +++ b/go/vt/vtgate/planbuilder/route_option.go @@ -258,6 +258,8 @@ func (ro *routeOption) computePlan(pb *primitiveBuilder, filter sqlparser.Expr) return ro.computeEqualPlan(pb, node) case sqlparser.InStr: return ro.computeINPlan(pb, node) + case sqlparser.NotInStr: + return ro.computeNotInPlan(node.Right), nil, nil } case *sqlparser.IsExpr: return ro.computeISPlan(pb, node) @@ -316,6 +318,10 @@ func (ro *routeOption) computeINPlan(pb *primitiveBuilder, comparison *sqlparser } switch node := comparison.Right.(type) { case sqlparser.ValTuple: + if len(node) == 1 && sqlparser.IsNull(node[0]) { + return engine.SelectNone, nil, nil + } + for _, n := range node { if !ro.exprIsValue(n) { return engine.SelectScatter, nil, nil @@ -328,6 +334,20 @@ func (ro *routeOption) computeINPlan(pb *primitiveBuilder, comparison *sqlparser return engine.SelectScatter, nil, nil } +// computeNotInPlan looks for null values to produce a SelectNone if found +func (*routeOption) computeNotInPlan(right sqlparser.Expr) engine.RouteOpcode { + switch node := right.(type) { + case sqlparser.ValTuple: + for _, n := range node { + if sqlparser.IsNull(n) { + return engine.SelectNone + } + } + } + + return engine.SelectScatter +} + var planCost = map[engine.RouteOpcode]int{ engine.SelectUnsharded: 0, engine.SelectNext: 0, diff --git a/go/vt/vtgate/planbuilder/testdata/filter_cases.txt b/go/vt/vtgate/planbuilder/testdata/filter_cases.txt index 74a3c7fa9a2..6ad9e9fc09c 100644 --- a/go/vt/vtgate/planbuilder/testdata/filter_cases.txt +++ b/go/vt/vtgate/planbuilder/testdata/filter_cases.txt @@ -1351,3 +1351,61 @@ "Table": "music" } } + +# Single table with unique vindex match and IN (null) +"select id from music where user_id = 4 and id IN (null)" +{ + "QueryType": "SELECT", + "Original": "select id from music where user_id = 4 and id IN (null)", + "Instructions": { + "OperatorType": "Route", + "Variant": "SelectNone", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from music where 1 != 1", + "Query": "select id from music where user_id = 4 and id in (null)", + "Table": "music" + } +} + +# Single table with unique vindex match and IN (null, 1, 2) +"select id from music where user_id = 4 and id IN (null, 1, 2)" +{ + "QueryType": "SELECT", + "Original": "select id from music where user_id = 4 and id IN (null, 1, 2)", + "Instructions": { + "OperatorType": "Route", + "Variant": "SelectEqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from music where 1 != 1", + "Query": "select id from music where user_id = 4 and id in (null, 1, 2)", + "Table": "music", + "Values": [ + 4 + ], + "Vindex": "user_index" + } +} + +# Single table with unique vindex match and NOT IN (null, 1, 2) +"select id from music where user_id = 4 and id NOT IN (null, 1, 2)" +{ + "QueryType": "SELECT", + "Original": "select id from music where user_id = 4 and id NOT IN (null, 1, 2)", + "Instructions": { + "OperatorType": "Route", + "Variant": "SelectNone", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from music where 1 != 1", + "Query": "select id from music where user_id = 4 and id not in (null, 1, 2)", + "Table": "music" + } +}