Skip to content

Commit

Permalink
expression: set correct return field type of builtin CONVERT() (#9305)
Browse files Browse the repository at this point in the history
  • Loading branch information
wuudjac authored and zz-jason committed Mar 31, 2019
1 parent e31e8f1 commit 37c128b
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 45 deletions.
12 changes: 6 additions & 6 deletions ddl/db_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func (s *testIntegrationSuite) TestInvalidDefault(c *C) {
c.Assert(terror.ErrorEqual(err, types.ErrInvalidDefault), IsTrue, Commentf("err %v", err))
}

// for issue #3848
// TestInvalidNameWhenCreateTable for issue #3848
func (s *testIntegrationSuite) TestInvalidNameWhenCreateTable(c *C) {
tk := testkit.NewTestKit(c, s.store)

Expand All @@ -145,7 +145,7 @@ func (s *testIntegrationSuite) TestInvalidNameWhenCreateTable(c *C) {
c.Assert(terror.ErrorEqual(err, ddl.ErrWrongDBName), IsTrue, Commentf("err %v", err))
}

// for issue #6879
// TestCreateTableIfNotExists for issue #6879
func (s *testIntegrationSuite) TestCreateTableIfNotExists(c *C) {
tk := testkit.NewTestKit(c, s.store)

Expand Down Expand Up @@ -580,7 +580,7 @@ func (s *testIntegrationSuite) TestChangingTableCharset(c *C) {
if rs != nil {
rs.Close()
}
c.Assert(err.Error(), Equals, "Unknown charset gbk")
c.Assert(err.Error(), Equals, "[parser:1115]Unknown character set: 'gbk'")
rs, err = tk.Exec("alter table t charset utf8")
if rs != nil {
rs.Close()
Expand Down Expand Up @@ -1238,9 +1238,9 @@ func (s *testIntegrationSuite) TestAlterAlgorithm(c *C) {
defer s.tk.MustExec("drop table if exists t")

s.tk.MustExec(`create table t(
a int,
b varchar(100),
c int,
a int,
b varchar(100),
c int,
INDEX idx_c(c)) PARTITION BY RANGE ( a ) (
PARTITION p0 VALUES LESS THAN (6),
PARTITION p1 VALUES LESS THAN (11),
Expand Down
44 changes: 34 additions & 10 deletions expression/builtin_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -924,9 +924,33 @@ func (c *convertFunctionClass) getFunction(ctx sessionctx.Context, args []Expres
return nil, err
}
bf := newBaseBuiltinFuncWithTp(ctx, args, types.ETString, types.ETString, types.ETString)
// TODO: issue #4436: The second parameter should be a constant.
// TODO: issue #4474: Charset supported by TiDB and MySQL is not the same.
// TODO: Fix #4436 && #4474, set the correct charset and flag of `bf.tp`.

charsetArg, ok := args[1].(*Constant)
if !ok {
// `args[1]` is limited by parser to be a constant string,
// should never go into here.
return nil, errIncorrectArgs.GenWithStackByArgs("charset")
}
transcodingName := charsetArg.Value.GetString()
bf.tp.Charset = strings.ToLower(transcodingName)
// Quoted about the behavior of syntax CONVERT(expr, type) to CHAR():
// In all cases, the string has the default collation for the character set.
// See https://dev.mysql.com/doc/refman/5.7/en/cast-functions.html#function_convert
// Here in syntax CONVERT(expr USING transcoding_name), behavior is kept the same,
// picking the default collation of target charset.
var err error
bf.tp.Collate, err = charset.GetDefaultCollation(bf.tp.Charset)
if err != nil {
return nil, errUnknownCharacterSet.GenWithStackByArgs(transcodingName)
}
// Result will be a binary string if converts charset to BINARY.
// See https://dev.mysql.com/doc/refman/5.7/en/charset-binary-set.html
if types.IsBinaryStr(bf.tp) {
types.SetBinChsClnFlag(bf.tp)
} else {
bf.tp.Flag &= ^mysql.BinaryFlag
}

bf.tp.Flen = mysql.MaxBlobWidth
sig := &builtinConvertSig{bf}
return sig, nil
Expand All @@ -943,21 +967,21 @@ func (b *builtinConvertSig) Clone() builtinFunc {
}

// evalString evals CONVERT(expr USING transcoding_name).
// Syntax CONVERT(expr, type) is parsed as cast expr so not handled here.
// See https://dev.mysql.com/doc/refman/5.7/en/cast-functions.html#function_convert
func (b *builtinConvertSig) evalString(row chunk.Row) (string, bool, error) {
expr, isNull, err := b.args[0].EvalString(b.ctx, row)
if isNull || err != nil {
return "", true, err
}

charsetName, isNull, err := b.args[1].EvalString(b.ctx, row)
if isNull || err != nil {
return "", true, err
}

encoding, _ := charset.Lookup(charsetName)
// Since charset is already validated and set from getFunction(), there's no
// need to get charset from args again.
encoding, _ := charset.Lookup(b.tp.Charset)
// However, if `b.tp.Charset` is abnormally set to a wrong charset, we still
// return with error.
if encoding == nil {
return "", true, errUnknownCharacterSet.GenWithStackByArgs(charsetName)
return "", true, errUnknownCharacterSet.GenWithStackByArgs(b.tp.Charset)
}

target, _, err := transform.String(encoding.NewDecoder(), expr)
Expand Down
57 changes: 42 additions & 15 deletions expression/builtin_string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -676,41 +676,61 @@ func (s *testEvaluatorSuite) TestSubstring(c *C) {
func (s *testEvaluatorSuite) TestConvert(c *C) {
defer testleak.AfterTest(c)()
tbl := []struct {
str string
cs string
result string
str interface{}
cs string
result string
hasBinaryFlag bool
}{
{"haha", "utf8", "haha"},
{"haha", "ascii", "haha"},
{"haha", "binary", "haha"},
{"haha", "utf8", "haha", false},
{"haha", "ascii", "haha", false},
{"haha", "binary", "haha", true},
{"haha", "bInAry", "haha", true},
{types.NewBinaryLiteralFromUint(0x7e, -1), "BiNarY", "~", true},
{types.NewBinaryLiteralFromUint(0xe4b8ade696870a, -1), "uTf8", "中文\n", false},
}
for _, v := range tbl {
fc := funcs[ast.Convert]
f, err := fc.getFunction(s.ctx, s.datumsToConstants(types.MakeDatums(v.str, v.cs)))
c.Assert(err, IsNil)
c.Assert(f, NotNil)
retType := f.getRetTp()
c.Assert(retType.Charset, Equals, strings.ToLower(v.cs))
collate, err := charset.GetDefaultCollation(strings.ToLower(v.cs))
c.Assert(err, IsNil)
c.Assert(retType.Collate, Equals, collate)
c.Assert(mysql.HasBinaryFlag(retType.Flag), Equals, v.hasBinaryFlag)

r, err := evalBuiltinFunc(f, chunk.Row{})
c.Assert(err, IsNil)
c.Assert(r.Kind(), Equals, types.KindString)
c.Assert(r.GetString(), Equals, v.result)
}

// Test case for error
// Test case for getFunction() error
errTbl := []struct {
str interface{}
cs string
result string
str interface{}
cs string
err string
}{
{"haha", "wrongcharset", "haha"},
{"haha", "wrongcharset", "[expression:1115]Unknown character set: 'wrongcharset'"},
{"haha", "cp866", "[expression:1115]Unknown character set: 'cp866'"},
}
for _, v := range errTbl {
fc := funcs[ast.Convert]
f, err := fc.getFunction(s.ctx, s.datumsToConstants(types.MakeDatums(v.str, v.cs)))
c.Assert(err, IsNil)
c.Assert(f, NotNil)
_, err = evalBuiltinFunc(f, chunk.Row{})
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, v.err)
c.Assert(f, IsNil)
}

// Test wrong charset while evaluating.
fc := funcs[ast.Convert]
f, err := fc.getFunction(s.ctx, s.datumsToConstants(types.MakeDatums("haha", "utf8")))
c.Assert(err, IsNil)
c.Assert(f, NotNil)
wrongFunction := f.(*builtinConvertSig)
wrongFunction.tp.Charset = "wrongcharset"
_, err = evalBuiltinFunc(wrongFunction, chunk.Row{})
c.Assert(err.Error(), Equals, "[expression:1115]Unknown character set: 'wrongcharset'")
}

func (s *testEvaluatorSuite) TestSubstringIndex(c *C) {
Expand Down Expand Up @@ -1199,6 +1219,13 @@ func (s *testEvaluatorSuite) TestChar(c *C) {
r, err := evalBuiltinFunc(f, chunk.Row{})
c.Assert(err, IsNil)
c.Assert(r, testutil.DatumEquals, types.NewDatum("AB"))

// Test unsupported charset.
fc = funcs[ast.CharFunc]
f, err = fc.getFunction(s.ctx, s.datumsToConstants(types.MakeDatums("65", "tidb")))
c.Assert(err, IsNil)
_, err = evalBuiltinFunc(f, chunk.Row{})
c.Assert(err.Error(), Equals, "unknown encoding: tidb")
}

func (s *testEvaluatorSuite) TestCharLength(c *C) {
Expand Down
20 changes: 13 additions & 7 deletions expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ func (s *testIntegrationSuite) TestStringBuiltin(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("use test")
ctx := context.Background()
var err error

// for length
tk.MustExec("drop table if exists t")
Expand Down Expand Up @@ -900,8 +901,16 @@ func (s *testIntegrationSuite) TestStringBuiltin(c *C) {
result.Check(testkit.Rows("'121' '0' '中文' <nil>"))

// for convert
result = tk.MustQuery(`select convert("123" using "866"), convert("123" using "binary"), convert("中文" using "binary"), convert("中文" using "utf8"), convert("中文" using "utf8mb4"), convert(cast("中文" as binary) using "utf8");`)
result.Check(testkit.Rows("123 123 中文 中文 中文 中文"))
result = tk.MustQuery(`select convert("123" using "binary"), convert("中文" using "binary"), convert("中文" using "utf8"), convert("中文" using "utf8mb4"), convert(cast("中文" as binary) using "utf8");`)
result.Check(testkit.Rows("123 中文 中文 中文 中文"))
// Charset 866 does not have a default collation configured currently, so this will return error.
err = tk.ExecToErr(`select convert("123" using "866");`)
c.Assert(err.Error(), Equals, "[parser:1115]Unknown character set: '866'")
// Test case in issue #4436.
tk.MustExec("drop table if exists t;")
tk.MustExec("create table t(a char(20));")
err = tk.ExecToErr("select convert(a using a) from t;")
c.Assert(err.Error(), Equals, "[parser:1115]Unknown character set: 'a'")

// for insert
result = tk.MustQuery(`select insert("中文", 1, 1, cast("aaa" as binary)), insert("ba", -1, 1, "aaa"), insert("ba", 1, 100, "aaa"), insert("ba", 100, 1, "aaa");`)
Expand Down Expand Up @@ -2362,11 +2371,8 @@ func (s *testIntegrationSuite) TestBuiltin(c *C) {
result.Check(testkit.Rows("ad\x01\x00Y"))
result = tk.MustQuery("select char(97, null, 100, 256, 89 using ascii)")
result.Check(testkit.Rows("ad\x01\x00Y"))
charRecordSet, err := tk.Exec("select char(97, null, 100, 256, 89 using tidb)")
c.Assert(err, IsNil)
c.Assert(charRecordSet, NotNil)
_, err = session.GetRows4Test(ctx, tk.Se, charRecordSet)
c.Assert(err.Error(), Equals, "unknown encoding: tidb")
err = tk.ExecToErr("select char(97, null, 100, 256, 89 using tidb)")
c.Assert(err.Error(), Equals, "[parser:1115]Unknown character set: 'tidb'")

// issue 3884
tk.MustExec("drop table if exists t")
Expand Down
8 changes: 4 additions & 4 deletions expression/typeinfer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,10 +456,10 @@ func (s *testInferTypeSuite) createTestCase4StrFuncs() []typeInferTestCase {
{"quote(c_float_d )", mysql.TypeVarString, charset.CharsetUTF8MB4, 0, 26, types.UnspecifiedLength},
{"quote(c_double_d )", mysql.TypeVarString, charset.CharsetUTF8MB4, 0, 46, types.UnspecifiedLength},

{"convert(c_double_d using c_text_d)", mysql.TypeLongBlob, charset.CharsetUTF8MB4, 0, mysql.MaxBlobWidth, types.UnspecifiedLength},
{"convert(c_binary using c_text_d)", mysql.TypeLongBlob, charset.CharsetUTF8MB4, 0, mysql.MaxBlobWidth, types.UnspecifiedLength},
{"convert(c_binary using c_binary)", mysql.TypeLongBlob, charset.CharsetUTF8MB4, 0, mysql.MaxBlobWidth, types.UnspecifiedLength},
{"convert(c_text_d using c_binary)", mysql.TypeLongBlob, charset.CharsetUTF8MB4, 0, mysql.MaxBlobWidth, types.UnspecifiedLength},
{"convert(c_double_d using utf8mb4)", mysql.TypeLongBlob, charset.CharsetUTF8MB4, 0, mysql.MaxBlobWidth, types.UnspecifiedLength},
{"convert(c_binary using utf8mb4)", mysql.TypeLongBlob, charset.CharsetUTF8MB4, 0, mysql.MaxBlobWidth, types.UnspecifiedLength},
{"convert(c_binary using 'binary')", mysql.TypeLongBlob, charset.CharsetBin, mysql.BinaryFlag, mysql.MaxBlobWidth, types.UnspecifiedLength},
{"convert(c_text_d using 'binary')", mysql.TypeLongBlob, charset.CharsetBin, mysql.BinaryFlag, mysql.MaxBlobWidth, types.UnspecifiedLength},

{"insert(c_varchar, c_int_d, c_int_d, c_varchar)", mysql.TypeLongBlob, charset.CharsetUTF8MB4, 0, mysql.MaxBlobWidth, types.UnspecifiedLength},
{"insert(c_varchar, c_int_d, c_int_d, c_binary)", mysql.TypeLongBlob, charset.CharsetBin, mysql.BinaryFlag, mysql.MaxBlobWidth, types.UnspecifiedLength},
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ require (
github.com/pingcap/goleveldb v0.0.0-20171020122428-b9ff6c35079e
github.com/pingcap/kvproto v0.0.0-20190215154024-7f2fc73ef562
github.com/pingcap/log v0.0.0-20190307075452-bd41d9273596
github.com/pingcap/parser v0.0.0-20190327115014-cc00dafe38cf
github.com/pingcap/parser v0.0.0-20190331024200-2f120db0a482
github.com/pingcap/pd v2.1.0-rc.4+incompatible
github.com/pingcap/tidb-tools v2.1.3-0.20190321065848-1e8b48f5c168+incompatible
github.com/pingcap/tipb v0.0.0-20190107072121-abbec73437b7
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ github.com/pingcap/kvproto v0.0.0-20190215154024-7f2fc73ef562 h1:32oF1/8lVnBR2JV
github.com/pingcap/kvproto v0.0.0-20190215154024-7f2fc73ef562/go.mod h1:QMdbTAXCHzzygQzqcG9uVUgU2fKeSN1GmfMiykdSzzY=
github.com/pingcap/log v0.0.0-20190307075452-bd41d9273596 h1:t2OQTpPJnrPDGlvA+3FwJptMTt6MEPdzK1Wt99oaefQ=
github.com/pingcap/log v0.0.0-20190307075452-bd41d9273596/go.mod h1:WpHUKhNZ18v116SvGrmjkA9CBhYmuUTKL+p8JC9ANEw=
github.com/pingcap/parser v0.0.0-20190327115014-cc00dafe38cf h1:QOpAuCYJYl710Ox0D0bCHQKU0HfAMRv/q/XVpOnWTqg=
github.com/pingcap/parser v0.0.0-20190327115014-cc00dafe38cf/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA=
github.com/pingcap/parser v0.0.0-20190331024200-2f120db0a482 h1:7WrNaktGabxJ/FUtML+wHEGPszCA1fXjWe82gt5Q/Eo=
github.com/pingcap/parser v0.0.0-20190331024200-2f120db0a482/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA=
github.com/pingcap/pd v2.1.0-rc.4+incompatible h1:/buwGk04aHO5odk/+O8ZOXGs4qkUjYTJ2UpCJXna8NE=
github.com/pingcap/pd v2.1.0-rc.4+incompatible/go.mod h1:nD3+EoYes4+aNNODO99ES59V83MZSI+dFbhyr667a0E=
github.com/pingcap/tidb-tools v2.1.3-0.20190321065848-1e8b48f5c168+incompatible h1:MkWCxgZpJBgY2f4HtwWMMFzSBb3+JPzeJgF3VrXE/bU=
Expand Down
10 changes: 10 additions & 0 deletions util/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/pingcap/errors"
"github.com/pingcap/parser"
"github.com/pingcap/parser/terror"
"github.com/pingcap/tidb/util/logutil"
"go.uber.org/zap"
)
Expand Down Expand Up @@ -113,6 +114,15 @@ func SyntaxError(err error) error {
return nil
}
logutil.Logger(context.Background()).Error("syntax error", zap.Error(err))

// If the error is already a terror with stack, pass it through.
if errors.HasStack(err) {
cause := errors.Cause(err)
if _, ok := cause.(*terror.Error); ok {
return err
}
}

return parser.ErrParse.GenWithStackByArgs(syntaxErrorPrefix, err.Error())
}

Expand Down

0 comments on commit 37c128b

Please sign in to comment.