Skip to content

Commit

Permalink
Cherry-pick 5a6f386 with conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
vitess-bot[bot] committed Jun 14, 2024
1 parent d406270 commit 67f6ebe
Show file tree
Hide file tree
Showing 17 changed files with 1,310 additions and 2 deletions.
171 changes: 171 additions & 0 deletions go/test/endtoend/vtgate/queries/misc/misc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,174 @@ func TestAnalyze(t *testing.T) {
})
}
}
<<<<<<< HEAD
=======

// TestTransactionModeVar executes SELECT on `transaction_mode` variable
func TestTransactionModeVar(t *testing.T) {
utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate")

mcmp, closer := start(t)
defer closer()

tcases := []struct {
setStmt string
expRes string
}{{
expRes: `[[VARCHAR("MULTI")]]`,
}, {
setStmt: `set transaction_mode = single`,
expRes: `[[VARCHAR("SINGLE")]]`,
}, {
setStmt: `set transaction_mode = multi`,
expRes: `[[VARCHAR("MULTI")]]`,
}, {
setStmt: `set transaction_mode = twopc`,
expRes: `[[VARCHAR("TWOPC")]]`,
}}

for _, tcase := range tcases {
mcmp.Run(tcase.setStmt, func(mcmp *utils.MySQLCompare) {
if tcase.setStmt != "" {
utils.Exec(t, mcmp.VtConn, tcase.setStmt)
}
utils.AssertMatches(t, mcmp.VtConn, "select @@transaction_mode", tcase.expRes)
})
}
}

// TestAliasesInOuterJoinQueries tests that aliases work in queries that have outer join clauses.
func TestAliasesInOuterJoinQueries(t *testing.T) {
utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate")

mcmp, closer := start(t)
defer closer()

// Insert data into the 2 tables
mcmp.Exec("insert into t1(id1, id2) values (1,2), (42,5), (5, 42)")
mcmp.Exec("insert into tbl(id, unq_col, nonunq_col) values (1,2,3), (2,5,3), (3, 42, 2)")

// Check that the select query works as intended and verifying the column names as well.
mcmp.ExecWithColumnCompare("select t1.id1 as t0, t1.id1 as t1, tbl.unq_col as col from t1 left outer join tbl on t1.id2 = tbl.nonunq_col")
mcmp.ExecWithColumnCompare("select t1.id1 as t0, t1.id1 as t1, tbl.unq_col as col from t1 left outer join tbl on t1.id2 = tbl.nonunq_col order by t1.id2 limit 2")
mcmp.ExecWithColumnCompare("select t1.id1 as t0, t1.id1 as t1, tbl.unq_col as col from t1 left outer join tbl on t1.id2 = tbl.nonunq_col order by t1.id2 limit 2 offset 2")
mcmp.ExecWithColumnCompare("select t1.id1 as t0, t1.id1 as t1, count(*) as leCount from t1 left outer join tbl on t1.id2 = tbl.nonunq_col group by 1, t1")
mcmp.ExecWithColumnCompare("select t.id1, t.id2, derived.unq_col from t1 t join (select id, unq_col, nonunq_col from tbl) as derived on t.id2 = derived.nonunq_col")
}

func TestAlterTableWithView(t *testing.T) {
utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate")
mcmp, closer := start(t)
defer closer()

// Test that create/alter view works and the output is as expected
mcmp.Exec(`use ks_misc`)
mcmp.Exec(`create view v1 as select * from t1`)
var viewDef string
utils.WaitForVschemaCondition(t, clusterInstance.VtgateProcess, keyspaceName, func(t *testing.T, ksMap map[string]any) bool {
views, ok := ksMap["views"]
if !ok {
return false
}
viewsMap := views.(map[string]any)
view, ok := viewsMap["v1"]
if ok {
viewDef = view.(string)
}
return ok
}, "Waiting for view creation")
mcmp.Exec(`insert into t1(id1, id2) values (1, 1)`)
mcmp.AssertMatches("select * from v1", `[[INT64(1) INT64(1)]]`)

// alter table add column
mcmp.Exec(`alter table t1 add column test bigint`)
time.Sleep(10 * time.Second)
mcmp.Exec(`alter view v1 as select * from t1`)

waitForChange := func(t *testing.T, ksMap map[string]any) bool {
// wait for the view definition to change
views := ksMap["views"]
viewsMap := views.(map[string]any)
newView := viewsMap["v1"]
if newView.(string) == viewDef {
return false
}
viewDef = newView.(string)
return true
}
utils.WaitForVschemaCondition(t, clusterInstance.VtgateProcess, keyspaceName, waitForChange, "Waiting for alter view")

mcmp.AssertMatches("select * from v1", `[[INT64(1) INT64(1) NULL]]`)

// alter table remove column
mcmp.Exec(`alter table t1 drop column test`)
mcmp.Exec(`alter view v1 as select * from t1`)

utils.WaitForVschemaCondition(t, clusterInstance.VtgateProcess, keyspaceName, waitForChange, "Waiting for alter view")

mcmp.AssertMatches("select * from v1", `[[INT64(1) INT64(1)]]`)
}

// TestStraightJoin tests that Vitess respects the ordering of join in a STRAIGHT JOIN query.
func TestStraightJoin(t *testing.T) {
utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate")
mcmp, closer := start(t)
defer closer()

mcmp.Exec("insert into tbl(id, unq_col, nonunq_col) values (1,0,10), (2,10,10), (3,4,20), (4,30,20), (5,40,10)")
mcmp.Exec(`insert into t1(id1, id2) values (10, 11), (20, 13)`)

mcmp.AssertMatchesNoOrder("select tbl.unq_col, tbl.nonunq_col, t1.id2 from t1 join tbl where t1.id1 = tbl.nonunq_col",
`[[INT64(0) INT64(10) INT64(11)] [INT64(10) INT64(10) INT64(11)] [INT64(4) INT64(20) INT64(13)] [INT64(40) INT64(10) INT64(11)] [INT64(30) INT64(20) INT64(13)]]`,
)
// Verify that in a normal join query, vitess joins tbl with t1.
res, err := mcmp.VtConn.ExecuteFetch("vexplain plan select tbl.unq_col, tbl.nonunq_col, t1.id2 from t1 join tbl where t1.id1 = tbl.nonunq_col", 100, false)
require.NoError(t, err)
require.Contains(t, fmt.Sprintf("%v", res.Rows), "tbl_t1")

// Test the same query with a straight join
mcmp.AssertMatchesNoOrder("select tbl.unq_col, tbl.nonunq_col, t1.id2 from t1 straight_join tbl where t1.id1 = tbl.nonunq_col",
`[[INT64(0) INT64(10) INT64(11)] [INT64(10) INT64(10) INT64(11)] [INT64(4) INT64(20) INT64(13)] [INT64(40) INT64(10) INT64(11)] [INT64(30) INT64(20) INT64(13)]]`,
)
// Verify that in a straight join query, vitess joins t1 with tbl.
res, err = mcmp.VtConn.ExecuteFetch("vexplain plan select tbl.unq_col, tbl.nonunq_col, t1.id2 from t1 straight_join tbl where t1.id1 = tbl.nonunq_col", 100, false)
require.NoError(t, err)
require.Contains(t, fmt.Sprintf("%v", res.Rows), "t1_tbl")
}

func TestColumnAliases(t *testing.T) {
utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate")
mcmp, closer := start(t)
defer closer()

mcmp.Exec("insert into t1(id1, id2) values (0,0), (1,1)")
mcmp.ExecWithColumnCompare(`select a as k from (select count(*) as a from t1) t`)
}

func TestHandleNullableColumn(t *testing.T) {
utils.SkipIfBinaryIsBelowVersion(t, 21, "vtgate")
require.NoError(t,
utils.WaitForAuthoritative(t, keyspaceName, "tbl", clusterInstance.VtgateProcess.ReadVSchema))
mcmp, closer := start(t)
defer closer()

mcmp.Exec("insert into t1(id1, id2) values (0,0), (1,1), (2,2)")
mcmp.Exec("insert into tbl(id, unq_col, nonunq_col) values (0,0,0), (1,1,6)")
// This query tests that we handle nullable columns correctly
// tbl.nonunq_col is not nullable according to the schema, but because of the left join, it can be NULL
mcmp.ExecWithColumnCompare(`select * from t1 left join tbl on t1.id2 = tbl.id where t1.id1 = 6 or tbl.nonunq_col = 6`)
}

func TestEnumSetVals(t *testing.T) {
utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate")

mcmp, closer := start(t)
defer closer()
require.NoError(t, utils.WaitForAuthoritative(t, keyspaceName, "tbl_enum_set", clusterInstance.VtgateProcess.ReadVSchema))

mcmp.Exec("insert into tbl_enum_set(id, enum_col, set_col) values (1, 'medium', 'a,b,e'), (2, 'small', 'e,f,g'), (3, 'large', 'c'), (4, 'xsmall', 'a,b'), (5, 'medium', 'a,d')")

mcmp.AssertMatches("select id, enum_col, cast(enum_col as signed) from tbl_enum_set order by enum_col, id", `[[INT64(4) ENUM("xsmall") INT64(1)] [INT64(2) ENUM("small") INT64(2)] [INT64(1) ENUM("medium") INT64(3)] [INT64(5) ENUM("medium") INT64(3)] [INT64(3) ENUM("large") INT64(4)]]`)
mcmp.AssertMatches("select id, set_col, cast(set_col as unsigned) from tbl_enum_set order by set_col, id", `[[INT64(4) SET("a,b") UINT64(3)] [INT64(3) SET("c") UINT64(4)] [INT64(5) SET("a,d") UINT64(9)] [INT64(1) SET("a,b,e") UINT64(19)] [INT64(2) SET("e,f,g") UINT64(112)]]`)
}
>>>>>>> 5a6f3868c5 (Handle Nullability for Columns from Outer Tables (#16174))
43 changes: 42 additions & 1 deletion go/test/endtoend/vtgate/queries/misc/schema.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,46 @@
<<<<<<< HEAD
create table if not exists t1(
id1 bigint,
id2 bigint,
primary key(id1)
) Engine=InnoDB;
) Engine=InnoDB;
=======
create table t1
(
id1 bigint,
id2 bigint,
primary key (id1)
) Engine=InnoDB;

create table unq_idx
(
unq_col bigint,
keyspace_id varbinary(20),
primary key (unq_col)
) Engine = InnoDB;

create table nonunq_idx
(
nonunq_col bigint,
id bigint,
keyspace_id varbinary(20),
primary key (nonunq_col, id)
) Engine = InnoDB;

create table tbl
(
id bigint,
unq_col bigint,
nonunq_col bigint not null,
primary key (id),
unique (unq_col)
) Engine = InnoDB;

create table tbl_enum_set
(
id bigint,
enum_col enum('xsmall', 'small', 'medium', 'large', 'xlarge'),
set_col set('a', 'b', 'c', 'd', 'e', 'f', 'g'),
primary key (id)
) Engine = InnoDB;
>>>>>>> 5a6f3868c5 (Handle Nullability for Columns from Outer Tables (#16174))
128 changes: 128 additions & 0 deletions go/vt/vtgate/evalengine/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,135 @@ type ctype struct {
Col collations.TypedCollation
}

<<<<<<< HEAD
func (ct ctype) nullable() bool {
=======
type Type struct {
typ sqltypes.Type
collation collations.ID
nullable bool
init bool
size, scale int32
values *EnumSetValues
}

func (v *EnumSetValues) Equal(other *EnumSetValues) bool {
if v == nil && other == nil {
return true
}
if v == nil || other == nil {
return false
}
return slices.Equal(*v, *other)
}

func NewType(t sqltypes.Type, collation collations.ID) Type {
// New types default to being nullable
return NewTypeEx(t, collation, true, 0, 0, nil)
}

func NewTypeEx(t sqltypes.Type, collation collations.ID, nullable bool, size, scale int32, values *EnumSetValues) Type {
return Type{
typ: t,
collation: collation,
nullable: nullable,
init: true,
size: size,
scale: scale,
values: values,
}
}

func NewTypeFromField(f *querypb.Field) Type {
return Type{
typ: f.Type,
collation: collations.ID(f.Charset),
nullable: f.Flags&uint32(querypb.MySqlFlag_NOT_NULL_FLAG) == 0,
init: true,
size: int32(f.ColumnLength),
scale: int32(f.Decimals),
}
}

func (t *Type) ToField(name string) *querypb.Field {
// need to get the proper flags for the type; usually leaving flags
// to 0 is OK, because Vitess' MySQL client will generate the right
// ones for the column's type, but here we're also setting the NotNull
// flag, so it needs to be set with the full flags for the column
_, flags := sqltypes.TypeToMySQL(t.typ)
if !t.nullable {
flags |= int64(querypb.MySqlFlag_NOT_NULL_FLAG)
}

f := &querypb.Field{
Name: name,
Type: t.typ,
Charset: uint32(t.collation),
ColumnLength: uint32(t.size),
Decimals: uint32(t.scale),
Flags: uint32(flags),
}
return f
}

func (t *Type) Type() sqltypes.Type {
if t.init {
return t.typ
}
return sqltypes.Unknown
}

func (t *Type) Collation() collations.ID {
return t.collation
}

func (t *Type) Size() int32 {
return t.size
}

func (t *Type) Scale() int32 {
return t.scale
}

func (t *Type) Nullable() bool {
if t.init {
return t.nullable
}
return true // nullable by default for unknown types
}

func (t *Type) SetNullability(n bool) {
t.nullable = n
}

func (t *Type) Values() *EnumSetValues {
return t.values
}

func (t *Type) Valid() bool {
return t.init
}

func (t *Type) Equal(other *Type) bool {
return t.typ == other.typ &&
t.collation == other.collation &&
t.nullable == other.nullable &&
t.size == other.size &&
t.scale == other.scale &&
t.values.Equal(other.values)
}

func (ct *ctype) equal(other ctype) bool {
return ct.Type == other.Type &&
ct.Flag == other.Flag &&
ct.Size == other.Size &&
ct.Scale == other.Scale &&
ct.Col == other.Col &&
ct.Values.Equal(other.Values)
}

func (ct *ctype) nullable() bool {
>>>>>>> 5a6f3868c5 (Handle Nullability for Columns from Outer Tables (#16174))
return ct.Flag&flagNullable != 0
}

Expand Down
Loading

0 comments on commit 67f6ebe

Please sign in to comment.