Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
26 changes: 26 additions & 0 deletions go/test/endtoend/vtgate/lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"strings"
"testing"

"vitess.io/vitess/go/test/utils"

"github.com/stretchr/testify/require"

"vitess.io/vitess/go/mysql"
Expand Down Expand Up @@ -503,6 +505,30 @@ func TestConsistentLookupUpdate(t *testing.T) {
require.Empty(t, qr.Rows)
}

func TestSelectNull(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 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)

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]]", "")

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\")]]", "")

exec(t, conn, "delete from t5_null_vindex")
}

func exec(t *testing.T, conn *mysql.Conn, query string) *sqltypes.Result {
t.Helper()
qr, err := conn.ExecuteFetch(query, 1000, true)
Expand Down
17 changes: 17 additions & 0 deletions go/test/endtoend/vtgate/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ create table t4_id2_idx(
id1 bigint,
keyspace_id varbinary(50),
primary key(id2, id1)
) Engine=InnoDB;

create table t5_null_vindex(
id bigint not null,
idx varchar(50),
primary key(id)
) Engine=InnoDB;`

VSchema = `
Expand All @@ -106,6 +112,9 @@ create table t4_id2_idx(
"hash": {
"type": "hash"
},
"xxhash": {
"type": "xxhash"
},
"t1_id2_vdx": {
"type": "consistent_lookup_unique",
"params": {
Expand Down Expand Up @@ -224,6 +233,14 @@ create table t4_id2_idx(
"name": "unicode_loose_md5"
}
]
},
"t5_null_vindex": {
"column_vindexes": [
{
"column": "idx",
"name": "xxhash"
}
]
},
"vstream_test": {
"column_vindexes": [
Expand Down
5 changes: 5 additions & 0 deletions go/vt/vtgate/engine/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ const (
SelectDBA
// SelectReference is for fetching from a reference table.
SelectReference
// SelectNone is used for queries that always return empty values
SelectNone
)

var routeName = map[RouteOpcode]string{
Expand All @@ -172,6 +174,7 @@ var routeName = map[RouteOpcode]string{
SelectNext: "SelectNext",
SelectDBA: "SelectDBA",
SelectReference: "SelectReference",
SelectNone: "SelectNone",
}

var (
Expand Down Expand Up @@ -230,6 +233,8 @@ func (route *Route) execute(vcursor VCursor, bindVars map[string]*querypb.BindVa
rss, bvs, err = route.paramsSelectEqual(vcursor, bindVars)
case SelectIN:
rss, bvs, err = route.paramsSelectIn(vcursor, bindVars)
case SelectNone:
rss, bvs, err = nil, nil, nil
default:
// Unreachable.
return nil, fmt.Errorf("unsupported query route: %v", route)
Expand Down
36 changes: 36 additions & 0 deletions go/vt/vtgate/engine/route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"errors"
"testing"

"github.com/stretchr/testify/require"

"vitess.io/vitess/go/mysql"
"vitess.io/vitess/go/sqltypes"
querypb "vitess.io/vitess/go/vt/proto/query"
Expand Down Expand Up @@ -148,6 +150,40 @@ func TestSelectEqualUnique(t *testing.T) {
expectResult(t, "sel.StreamExecute", result, defaultSelectResult)
}

func TestSelectNone(t *testing.T) {
vindex, _ := vindexes.NewHash("", nil)
sel := NewRoute(
SelectNone,
&vindexes.Keyspace{
Name: "ks",
Sharded: true,
},
"dummy_select",
"dummy_select_field",
)
sel.Vindex = vindex.(vindexes.SingleColumn)
sel.Values = nil

vc := &loggingVCursor{
shards: []string{"-20", "20-"},
results: []*sqltypes.Result{},
}
result, err := sel.Execute(vc, map[string]*querypb.BindVariable{}, false)
require.NoError(t, err)
require.Empty(t, vc.log)
expectResult(t, "sel.Execute", result, &sqltypes.Result{})

vc.Rewind()

result, err = sel.Execute(vc, map[string]*querypb.BindVariable{}, true)
require.NoError(t, err)
vc.ExpectLog(t, []string{
`ResolveDestinations ks [] Destinations:DestinationAnyShard()`,
`ExecuteMultiShard ks.-20: dummy_select_field {} false false`,
})
expectResult(t, "sel.Execute", result, &sqltypes.Result{})
}

func TestSelectEqualUniqueScatter(t *testing.T) {
vindex, _ := vindexes.NewLookupUnique("", map[string]string{
"table": "lkp",
Expand Down
32 changes: 30 additions & 2 deletions go/vt/vtgate/planbuilder/route_option.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,19 @@ func (ro *routeOption) canMergeOnFilter(pb *primitiveBuilder, rro *routeOption,
// the route.
func (ro *routeOption) UpdatePlan(pb *primitiveBuilder, filter sqlparser.Expr) {
switch ro.eroute.Opcode {
case engine.SelectUnsharded, engine.SelectNext, engine.SelectDBA, engine.SelectReference:
// For these opcodes, a new filter will not make any difference, so we can just exit early
case engine.SelectUnsharded, engine.SelectNext, engine.SelectDBA, engine.SelectReference, engine.SelectNone:
return
}
opcode, vindex, values := ro.computePlan(pb, filter)
if opcode == engine.SelectScatter {
return
}
// If we get SelectNone in next filters, override the previous route plan.
if opcode == engine.SelectNone {
ro.updateRoute(opcode, vindex, values)
return
}
switch ro.eroute.Opcode {
case engine.SelectEqualUnique:
if opcode == engine.SelectEqualUnique && vindex.Cost() < ro.eroute.Vindex.Cost() {
Expand All @@ -231,7 +237,7 @@ func (ro *routeOption) UpdatePlan(pb *primitiveBuilder, filter sqlparser.Expr) {
}
case engine.SelectScatter:
switch opcode {
case engine.SelectEqualUnique, engine.SelectEqual, engine.SelectIN:
case engine.SelectEqualUnique, engine.SelectEqual, engine.SelectIN, engine.SelectNone:
ro.updateRoute(opcode, vindex, values)
}
}
Expand All @@ -253,6 +259,8 @@ func (ro *routeOption) computePlan(pb *primitiveBuilder, filter sqlparser.Expr)
case sqlparser.InStr:
return ro.computeINPlan(pb, node)
}
case *sqlparser.IsExpr:
return ro.computeISPlan(pb, node)
}
return engine.SelectScatter, nil, nil
}
Expand All @@ -269,6 +277,9 @@ func (ro *routeOption) computeEqualPlan(pb *primitiveBuilder, comparison *sqlpar
return engine.SelectScatter, nil, nil
}
}
if sqlparser.IsNull(right) {
return engine.SelectNone, nil, nil
}
if !ro.exprIsValue(right) {
return engine.SelectScatter, nil, nil
}
Expand All @@ -278,6 +289,23 @@ func (ro *routeOption) computeEqualPlan(pb *primitiveBuilder, comparison *sqlpar
return engine.SelectEqual, vindex, right
}

// computeEqualPlan computes the plan for an equality constraint.
func (ro *routeOption) computeISPlan(pb *primitiveBuilder, comparison *sqlparser.IsExpr) (opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) {
// we only handle IS NULL correct. IsExpr can contain other expressions as well
if comparison.Operator != sqlparser.IsNullStr {
return engine.SelectScatter, nil, nil
}

vindex = ro.FindVindex(pb, comparison.Expr)
if vindex == nil {
return engine.SelectScatter, nil, nil
}
if vindex.IsUnique() {
return engine.SelectEqualUnique, vindex, &sqlparser.NullVal{}
}
return engine.SelectEqual, vindex, &sqlparser.NullVal{}
}

// computeINPlan computes the plan for an IN constraint.
func (ro *routeOption) computeINPlan(pb *primitiveBuilder, comparison *sqlparser.ComparisonExpr) (opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) {
vindex = ro.FindVindex(pb, comparison.Left)
Expand Down
76 changes: 76 additions & 0 deletions go/vt/vtgate/planbuilder/testdata/filter_cases.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1257,3 +1257,79 @@
# and the second reference is to the innermost 'from' subquery.
"select id2 from user uu where id in (select id from user where id = uu.id and user.col in (select col from (select id from user_extra where user_id = 5) uu where uu.user_id = uu.id))"
"unsupported: cross-shard correlated subquery"

# Select with equals null
"select id from music where id = null"
{
"QueryType": "SELECT",
"Original": "select id from music where id = 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 id = null",
"Table": "music"
}
}

# SELECT with IS NULL
"select id from music where id is null"
{
"QueryType": "SELECT",
"Original": "select id from music where id is null",
"Instructions": {
"OperatorType": "Route",
"Variant": "SelectEqualUnique",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select id from music where 1 != 1",
"Query": "select id from music where id is null",
"Table": "music",
"Values": [
null
],
"Vindex": "music_user_map"
}
}

# SELECT with IS NOT NULL
"select id from music where id is not null"
{
"QueryType": "SELECT",
"Original": "select id from music where id is not null",
"Instructions": {
"OperatorType": "Route",
"Variant": "SelectScatter",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select id from music where 1 != 1",
"Query": "select id from music where id is not null",
"Table": "music"
}
}

# Single table with unique vindex match and null match
"select id from music where user_id = 4 and id = null"
{
"QueryType": "SELECT",
"Original": "select id from music where user_id = 4 and id = 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 = null",
"Table": "music"
}
}