From f7e4858344acc84f19340189c7abc6501f5991d6 Mon Sep 17 00:00:00 2001 From: Zhang Jian Date: Tue, 14 May 2019 16:15:31 +0800 Subject: [PATCH] executor: correct range calculation for CHAR column (#10124) --- executor/executor_test.go | 1 + executor/point_get.go | 18 ++- executor/point_get_test.go | 287 ++++++++++++++++++++++++++++++++++++- util/ranger/points.go | 76 +++++++++- util/testkit/testkit.go | 23 +++ 5 files changed, 397 insertions(+), 8 deletions(-) diff --git a/executor/executor_test.go b/executor/executor_test.go index 4306ec8153210..8faddae1125b3 100644 --- a/executor/executor_test.go +++ b/executor/executor_test.go @@ -81,6 +81,7 @@ var _ = Suite(&testContextOptionSuite{}) var _ = Suite(&testBypassSuite{}) var _ = Suite(&testUpdateSuite{}) var _ = Suite(&testOOMSuite{}) +var _ = Suite(&testPointGetSuite{}) type testSuite struct { cluster *mocktikv.Cluster diff --git a/executor/point_get.go b/executor/point_get.go index 1523f8ea90eb0..ece08e70c1eac 100644 --- a/executor/point_get.go +++ b/executor/point_get.go @@ -28,6 +28,7 @@ import ( "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/codec" + "github.com/pingcap/tidb/util/ranger" "golang.org/x/net/context" ) @@ -86,8 +87,8 @@ func (e *PointGetExecutor) Next(ctx context.Context, chk *chunk.Chunk) error { } if e.idxInfo != nil { idxKey, err1 := e.encodeIndexKey() - if err1 != nil { - return errors.Trace(err1) + if err1 != nil && !kv.ErrNotExist.Equal(err1) { + return err1 } handleVal, err1 := e.get(idxKey) @@ -132,16 +133,21 @@ func (e *PointGetExecutor) Next(ctx context.Context, chk *chunk.Chunk) error { return e.decodeRowValToChunk(val, chk) } -func (e *PointGetExecutor) encodeIndexKey() ([]byte, error) { +func (e *PointGetExecutor) encodeIndexKey() (_ []byte, err error) { + sc := e.ctx.GetSessionVars().StmtCtx for i := range e.idxVals { colInfo := e.tblInfo.Columns[e.idxInfo.Columns[i].Offset] - casted, err := table.CastValue(e.ctx, e.idxVals[i], colInfo) + if colInfo.Tp == mysql.TypeString || colInfo.Tp == mysql.TypeVarString || colInfo.Tp == mysql.TypeVarchar { + e.idxVals[i], err = ranger.HandlePadCharToFullLength(sc, &colInfo.FieldType, e.idxVals[i]) + } else { + e.idxVals[i], err = table.CastValue(e.ctx, e.idxVals[i], colInfo) + } if err != nil { return nil, errors.Trace(err) } - e.idxVals[i] = casted } - encodedIdxVals, err := codec.EncodeKey(e.ctx.GetSessionVars().StmtCtx, nil, e.idxVals...) + + encodedIdxVals, err := codec.EncodeKey(sc, nil, e.idxVals...) if err != nil { return nil, errors.Trace(err) } diff --git a/executor/point_get_test.go b/executor/point_get_test.go index 65f8786ea7f71..f04c381354a49 100644 --- a/executor/point_get_test.go +++ b/executor/point_get_test.go @@ -14,11 +14,57 @@ package executor_test import ( + "fmt" + . "github.com/pingcap/check" + "github.com/pingcap/tidb/domain" + "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/session" + "github.com/pingcap/tidb/store/mockstore" + "github.com/pingcap/tidb/store/tikv" "github.com/pingcap/tidb/util/testkit" ) -func (s *testSuite) TestPointGet(c *C) { +type testPointGetSuite struct { + store kv.Storage + dom *domain.Domain + cli *checkRequestClient +} + +func (s *testPointGetSuite) SetUpSuite(c *C) { + cli := &checkRequestClient{} + hijackClient := func(c tikv.Client) tikv.Client { + cli.Client = c + return cli + } + s.cli = cli + + var err error + s.store, err = mockstore.NewMockTikvStore( + mockstore.WithHijackClient(hijackClient), + ) + c.Assert(err, IsNil) + s.dom, err = session.BootstrapSession(s.store) + c.Assert(err, IsNil) + s.dom.SetStatsUpdating(true) +} + +func (s *testPointGetSuite) TearDownSuite(c *C) { + s.dom.Close() + s.store.Close() +} + +func (s *testPointGetSuite) TearDownTest(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("use test") + r := tk.MustQuery("show tables") + for _, tb := range r.Rows() { + tableName := tb[0] + tk.MustExec(fmt.Sprintf("drop table %v", tableName)) + } +} + +func (s *testPointGetSuite) TestPointGet(c *C) { tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") tk.MustExec("create table point (id int primary key, c int, d varchar(10), unique c_d (c, d))") @@ -55,3 +101,242 @@ func (s *testSuite) TestPointGet(c *C) { `5 5 6 5 6 7 6 7 7`, )) } + +func (s *testPointGetSuite) TestPointGetCharPK(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec(`use test;`) + tk.MustExec(`drop table if exists t;`) + tk.MustExec(`create table t(a char(2) primary key, b char(2));`) + tk.MustExec(`insert into t values("aa", "bb");`) + + // Test truncate without sql mode `PAD_CHAR_TO_FULL_LENGTH`. + tk.MustExec(`set @@sql_mode="";`) + tk.MustPointGet(`select * from t where a = "aa";`).Check(testkit.Rows(`aa bb`)) + tk.MustPointGet(`select * from t where a = "aab";`).Check(testkit.Rows()) + + // Test truncate with sql mode `PAD_CHAR_TO_FULL_LENGTH`. + tk.MustExec(`set @@sql_mode="PAD_CHAR_TO_FULL_LENGTH";`) + tk.MustPointGet(`select * from t where a = "aa";`).Check(testkit.Rows(`aa bb`)) + tk.MustPointGet(`select * from t where a = "aab";`).Check(testkit.Rows()) + + tk.MustExec(`truncate table t;`) + tk.MustExec(`insert into t values("a ", "b ");`) + + // Test trailing spaces without sql mode `PAD_CHAR_TO_FULL_LENGTH`. + tk.MustExec(`set @@sql_mode="";`) + tk.MustPointGet(`select * from t where a = "a";`).Check(testkit.Rows(`a b`)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows()) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows()) + + // Test trailing spaces with sql mode `PAD_CHAR_TO_FULL_LENGTH`. + tk.MustExec(`set @@sql_mode="PAD_CHAR_TO_FULL_LENGTH";`) + tk.MustPointGet(`select * from t where a = "a";`).Check(testkit.Rows()) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows(`a b`)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows()) + + // // Test CHAR BINARY. + tk.MustExec(`drop table if exists t;`) + tk.MustExec(`create table t(a char(2) binary primary key, b char(2));`) + tk.MustExec(`insert into t values(" ", " ");`) + tk.MustExec(`insert into t values("a ", "b ");`) + + // Test trailing spaces without sql mode `PAD_CHAR_TO_FULL_LENGTH`. + tk.MustExec(`set @@sql_mode="";`) + tk.MustPointGet(`select * from t where a = "a";`).Check(testkit.Rows(`a b`)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows(`a b`)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows(`a b`)) + tk.MustPointGet(`select * from t where a = " ";`).Check(testkit.Rows(` `)) + tk.MustPointGet(`select * from t where a = " ";`).Check(testkit.Rows(` `)) + tk.MustPointGet(`select * from t where a = " ";`).Check(testkit.Rows(` `)) + + // Test trailing spaces with sql mode `PAD_CHAR_TO_FULL_LENGTH`. + tk.MustExec(`set @@sql_mode="PAD_CHAR_TO_FULL_LENGTH";`) + tk.MustPointGet(`select * from t where a = "a";`).Check(testkit.Rows(`a b`)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows(`a b`)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows(`a b`)) + tk.MustPointGet(`select * from t where a = " ";`).Check(testkit.Rows(` `)) + tk.MustPointGet(`select * from t where a = " ";`).Check(testkit.Rows(` `)) + tk.MustPointGet(`select * from t where a = " ";`).Check(testkit.Rows(` `)) +} + +func (s *testPointGetSuite) TestIndexLookupCharPK(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec(`use test;`) + tk.MustExec(`drop table if exists t;`) + tk.MustExec(`create table t(a char(2) primary key, b char(2));`) + tk.MustExec(`insert into t values("aa", "bb");`) + + // Test truncate without sql mode `PAD_CHAR_TO_FULL_LENGTH`. + tk.MustExec(`set @@sql_mode="";`) + tk.MustIndexLookup(`select * from t tmp where a = "aa";`).Check(testkit.Rows(`aa bb`)) + tk.MustIndexLookup(`select * from t tmp where a = "aab";`).Check(testkit.Rows()) + + // Test truncate with sql mode `PAD_CHAR_TO_FULL_LENGTH`. + tk.MustExec(`set @@sql_mode="PAD_CHAR_TO_FULL_LENGTH";`) + tk.MustIndexLookup(`select * from t tmp where a = "aa";`).Check(testkit.Rows(`aa bb`)) + tk.MustIndexLookup(`select * from t tmp where a = "aab";`).Check(testkit.Rows()) + + tk.MustExec(`truncate table t;`) + tk.MustExec(`insert into t values("a ", "b ");`) + + // Test trailing spaces without sql mode `PAD_CHAR_TO_FULL_LENGTH`. + tk.MustExec(`set @@sql_mode="";`) + tk.MustIndexLookup(`select * from t tmp where a = "a";`).Check(testkit.Rows(`a b`)) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows()) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows()) + + // Test trailing spaces with sql mode `PAD_CHAR_TO_FULL_LENGTH`. + tk.MustExec(`set @@sql_mode="PAD_CHAR_TO_FULL_LENGTH";`) + tk.MustIndexLookup(`select * from t tmp where a = "a";`).Check(testkit.Rows()) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows(`a b`)) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows()) + + // Test CHAR BINARY. + tk.MustExec(`drop table if exists t;`) + tk.MustExec(`create table t(a char(2) binary primary key, b char(2));`) + tk.MustExec(`insert into t values(" ", " ");`) + tk.MustExec(`insert into t values("a ", "b ");`) + + // Test trailing spaces without sql mode `PAD_CHAR_TO_FULL_LENGTH`. + tk.MustExec(`set @@sql_mode="";`) + tk.MustIndexLookup(`select * from t tmp where a = "a";`).Check(testkit.Rows(`a b`)) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows(`a b`)) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows(`a b`)) + tk.MustIndexLookup(`select * from t tmp where a = " ";`).Check(testkit.Rows(` `)) + tk.MustIndexLookup(`select * from t tmp where a = " ";`).Check(testkit.Rows(` `)) + tk.MustIndexLookup(`select * from t tmp where a = " ";`).Check(testkit.Rows(` `)) + + // Test trailing spaces with sql mode `PAD_CHAR_TO_FULL_LENGTH`. + tk.MustExec(`set @@sql_mode="PAD_CHAR_TO_FULL_LENGTH";`) + tk.MustIndexLookup(`select * from t tmp where a = "a";`).Check(testkit.Rows(`a b`)) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows(`a b`)) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows(`a b`)) + tk.MustIndexLookup(`select * from t tmp where a = " ";`).Check(testkit.Rows(` `)) + tk.MustIndexLookup(`select * from t tmp where a = " ";`).Check(testkit.Rows(` `)) + tk.MustIndexLookup(`select * from t tmp where a = " ";`).Check(testkit.Rows(` `)) +} + +func (s *testPointGetSuite) TestPointGetVarcharPK(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec(`use test;`) + tk.MustExec(`drop table if exists t;`) + tk.MustExec(`create table t(a varchar(2) primary key, b varchar(2));`) + tk.MustExec(`insert into t values("aa", "bb");`) + + // Test truncate without sql mode `PAD_CHAR_TO_FULL_LENGTH`. + // `PAD_CHAR_TO_FULL_LENGTH` should not affect the result. + tk.MustExec(`set @@sql_mode="";`) + tk.MustPointGet(`select * from t where a = "aa";`).Check(testkit.Rows(`aa bb`)) + tk.MustPointGet(`select * from t where a = "aab";`).Check(testkit.Rows()) + + // Test truncate with sql mode `PAD_CHAR_TO_FULL_LENGTH`. + // `PAD_CHAR_TO_FULL_LENGTH` should not affect the result. + tk.MustExec(`set @@sql_mode="PAD_CHAR_TO_FULL_LENGTH";`) + tk.MustPointGet(`select * from t where a = "aa";`).Check(testkit.Rows(`aa bb`)) + tk.MustPointGet(`select * from t where a = "aab";`).Check(testkit.Rows()) + + tk.MustExec(`truncate table t;`) + tk.MustExec(`insert into t values("a ", "b ");`) + + // Test trailing spaces without sql mode `PAD_CHAR_TO_FULL_LENGTH`. + // `PAD_CHAR_TO_FULL_LENGTH` should not affect the result. + tk.MustExec(`set @@sql_mode="";`) + tk.MustPointGet(`select * from t where a = "a";`).Check(testkit.Rows()) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows(`a b `)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows()) + + // Test trailing spaces with sql mode `PAD_CHAR_TO_FULL_LENGTH`. + // `PAD_CHAR_TO_FULL_LENGTH` should not affect the result. + tk.MustExec(`set @@sql_mode="PAD_CHAR_TO_FULL_LENGTH";`) + tk.MustPointGet(`select * from t where a = "a";`).Check(testkit.Rows()) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows(`a b `)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows()) + + // // Test VARCHAR BINARY. + tk.MustExec(`drop table if exists t;`) + tk.MustExec(`create table t(a varchar(2) binary primary key, b varchar(2));`) + tk.MustExec(`insert into t values(" ", " ");`) + tk.MustExec(`insert into t values("a ", "b ");`) + + // Test trailing spaces without sql mode `PAD_CHAR_TO_FULL_LENGTH`. + // `PAD_CHAR_TO_FULL_LENGTH` should not affect the result. + tk.MustExec(`set @@sql_mode="";`) + tk.MustPointGet(`select * from t where a = "a";`).Check(testkit.Rows(`a b `)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows(`a b `)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows(`a b `)) + tk.MustPointGet(`select * from t where a = " ";`).Check(testkit.Rows(` `)) + tk.MustPointGet(`select * from t where a = " ";`).Check(testkit.Rows(` `)) + tk.MustPointGet(`select * from t where a = " ";`).Check(testkit.Rows(` `)) + + // Test trailing spaces with sql mode `PAD_CHAR_TO_FULL_LENGTH`. + // `PAD_CHAR_TO_FULL_LENGTH` should not affect the result. + tk.MustExec(`set @@sql_mode="PAD_CHAR_TO_FULL_LENGTH";`) + tk.MustPointGet(`select * from t where a = "a";`).Check(testkit.Rows(`a b `)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows(`a b `)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows(`a b `)) + tk.MustPointGet(`select * from t where a = " ";`).Check(testkit.Rows(` `)) + tk.MustPointGet(`select * from t where a = " ";`).Check(testkit.Rows(` `)) + tk.MustPointGet(`select * from t where a = " ";`).Check(testkit.Rows(` `)) +} + +func (s *testPointGetSuite) TestPointGetBinaryPK(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec(`use test;`) + tk.MustExec(`drop table if exists t;`) + tk.MustExec(`create table t(a binary(2) primary key, b binary(2));`) + tk.MustExec(`insert into t values("a", "b");`) + + tk.MustExec(`set @@sql_mode="";`) + tk.MustPointGet(`select * from t where a = "a";`).Check(testkit.Rows()) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows()) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows()) + tk.MustPointGet(`select * from t where a = "a\0";`).Check(testkit.Rows("a\x00 b\x00")) + + // `PAD_CHAR_TO_FULL_LENGTH` should not affect the result. + tk.MustExec(`set @@sql_mode="PAD_CHAR_TO_FULL_LENGTH";`) + tk.MustPointGet(`select * from t where a = "a";`).Check(testkit.Rows()) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows()) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows()) + tk.MustPointGet(`select * from t where a = "a\0";`).Check(testkit.Rows("a\x00 b\x00")) + + tk.MustExec(`insert into t values("a ", "b ");`) + tk.MustPointGet(`select * from t where a = "a";`).Check(testkit.Rows()) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows(`a b `)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows()) + + // `PAD_CHAR_TO_FULL_LENGTH` should not affect the result. + tk.MustPointGet(`select * from t where a = "a";`).Check(testkit.Rows()) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows(`a b `)) + tk.MustPointGet(`select * from t where a = "a ";`).Check(testkit.Rows()) +} + +func (s *testPointGetSuite) TestIndexLookupBinaryPK(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec(`use test;`) + tk.MustExec(`drop table if exists t;`) + tk.MustExec(`create table t(a binary(2) primary key, b binary(2));`) + tk.MustExec(`insert into t values("a", "b");`) + + tk.MustExec(`set @@sql_mode="";`) + tk.MustIndexLookup(`select * from t tmp where a = "a";`).Check(testkit.Rows()) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows()) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows()) + tk.MustIndexLookup(`select * from t tmp where a = "a\0";`).Check(testkit.Rows("a\x00 b\x00")) + + // `PAD_CHAR_TO_FULL_LENGTH` should not affect the result. + tk.MustExec(`set @@sql_mode="PAD_CHAR_TO_FULL_LENGTH";`) + tk.MustIndexLookup(`select * from t tmp where a = "a";`).Check(testkit.Rows()) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows()) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows()) + tk.MustIndexLookup(`select * from t tmp where a = "a\0";`).Check(testkit.Rows("a\x00 b\x00")) + + tk.MustExec(`insert into t values("a ", "b ");`) + tk.MustIndexLookup(`select * from t tmp where a = "a";`).Check(testkit.Rows()) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows(`a b `)) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows()) + + // `PAD_CHAR_TO_FULL_LENGTH` should not affect the result. + tk.MustIndexLookup(`select * from t tmp where a = "a";`).Check(testkit.Rows()) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows(`a b `)) + tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows()) +} diff --git a/util/ranger/points.go b/util/ranger/points.go index 9e73643068394..75ae0a793ed3b 100644 --- a/util/ranger/points.go +++ b/util/ranger/points.go @@ -17,12 +17,15 @@ import ( "fmt" "math" "sort" + "strings" "github.com/pingcap/errors" "github.com/pingcap/parser/ast" + "github.com/pingcap/parser/charset" "github.com/pingcap/parser/mysql" "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/expression" + "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/sessionctx/stmtctx" "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util/chunk" @@ -208,11 +211,19 @@ func (r *builder) buildFormBinOp(expr *expression.ScalarFunction) []point { op string value types.Datum err error + ft *types.FieldType ) - if _, ok := expr.GetArgs()[0].(*expression.Column); ok { + if col, ok := expr.GetArgs()[0].(*expression.Column); ok { value, err = expr.GetArgs()[1].Eval(chunk.Row{}) op = expr.FuncName.L + ft = col.RetType } else { + col, ok := expr.GetArgs()[1].(*expression.Column) + if !ok { + return nil + } + ft = col.RetType + value, err = expr.GetArgs()[0].Eval(chunk.Row{}) switch expr.FuncName.L { case ast.GE: @@ -234,6 +245,11 @@ func (r *builder) buildFormBinOp(expr *expression.ScalarFunction) []point { return nil } + value, err = HandlePadCharToFullLength(r.sc, ft, value) + if err != nil { + return nil + } + switch op { case ast.EQ: startPoint := point{value: value, start: true} @@ -265,6 +281,64 @@ func (r *builder) buildFormBinOp(expr *expression.ScalarFunction) []point { return nil } +// HandlePadCharToFullLength handles the "PAD_CHAR_TO_FULL_LENGTH" sql mode for +// CHAR[N] index columns. +// NOTE: kv.ErrNotExist is returned to indicate that this value can not match +// any (key, value) pair in tikv storage. This error should be handled by +// the caller. +func HandlePadCharToFullLength(sc *stmtctx.StatementContext, ft *types.FieldType, val types.Datum) (types.Datum, error) { + isChar := (ft.Tp == mysql.TypeString) + isBinary := (isChar && ft.Collate == charset.CollationBin) + isVarchar := (ft.Tp == mysql.TypeVarString || ft.Tp == mysql.TypeVarchar) + isVarBinary := (isVarchar && ft.Collate == charset.CollationBin) + + if !isChar && !isVarchar && !isBinary && !isVarBinary { + return val, nil + } + + hasBinaryFlag := mysql.HasBinaryFlag(ft.Flag) + targetStr, err := val.ToString() + if err != nil { + return val, err + } + + switch { + case isBinary || isVarBinary: + val.SetString(targetStr) + return val, nil + case isVarchar && hasBinaryFlag: + noTrailingSpace := strings.TrimRight(targetStr, " ") + if numSpacesToFill := ft.Flen - len(noTrailingSpace); numSpacesToFill > 0 { + noTrailingSpace += strings.Repeat(" ", numSpacesToFill) + } + val.SetString(noTrailingSpace) + return val, nil + case isVarchar && !hasBinaryFlag: + val.SetString(targetStr) + return val, nil + case isChar && hasBinaryFlag: + noTrailingSpace := strings.TrimRight(targetStr, " ") + val.SetString(noTrailingSpace) + return val, nil + case isChar && !hasBinaryFlag && !sc.PadCharToFullLength: + val.SetString(targetStr) + return val, nil + case isChar && !hasBinaryFlag && sc.PadCharToFullLength: + if len(targetStr) != ft.Flen { + // return kv.ErrNotExist to indicate that this value can not match any + // (key, value) pair in tikv storage. + return val, kv.ErrNotExist + } + // Trailing spaces of data typed "CHAR[N]" is trimed in the storage, we + // need to trim these trailing spaces as well. + noTrailingSpace := strings.TrimRight(targetStr, " ") + val.SetString(noTrailingSpace) + return val, nil + default: + return val, nil + } +} + func (r *builder) buildFromIsTrue(expr *expression.ScalarFunction, isNot int) []point { if isNot == 1 { // NOT TRUE range is {[null null] [0, 0]} diff --git a/util/testkit/testkit.go b/util/testkit/testkit.go index ca12e8a83cd3f..a3b5f31f6486a 100644 --- a/util/testkit/testkit.go +++ b/util/testkit/testkit.go @@ -17,6 +17,7 @@ import ( "bytes" "fmt" "sort" + "strings" "sync/atomic" "github.com/pingcap/check" @@ -170,6 +171,28 @@ func (tk *TestKit) MustExec(sql string, args ...interface{}) { } } +// MustIndexLookup checks whether the plan for the sql is Point_Get. +func (tk *TestKit) MustIndexLookup(sql string, args ...interface{}) *Result { + rs := tk.MustQuery("explain "+sql, args...) + hasIndexLookup := false + for i := range rs.rows { + if strings.Contains(rs.rows[i][0], "IndexLookUp") { + hasIndexLookup = true + break + } + } + tk.c.Assert(hasIndexLookup, check.IsTrue) + return tk.MustQuery(sql, args...) +} + +// MustPointGet checks whether the plan for the sql is Point_Get. +func (tk *TestKit) MustPointGet(sql string, args ...interface{}) *Result { + rs := tk.MustQuery("explain "+sql, args...) + tk.c.Assert(len(rs.rows), check.Equals, 1) + tk.c.Assert(strings.Contains(rs.rows[0][0], "Point_Get"), check.IsTrue) + return tk.MustQuery(sql, args...) +} + // MustQuery query the statements and returns result rows. // If expected result is set it asserts the query result equals expected result. func (tk *TestKit) MustQuery(sql string, args ...interface{}) *Result {