diff --git a/expression/builtin_string.go b/expression/builtin_string.go index 1daf34280ea0d..7482bf13a573d 100644 --- a/expression/builtin_string.go +++ b/expression/builtin_string.go @@ -1695,7 +1695,47 @@ type builtinInstrSig struct { // See https://dev.mysql.com/doc/refman/5.6/en/string-functions.html#function_instr func (b *builtinInstrSig) eval(row []types.Datum) (d types.Datum, err error) { - return d, errFunctionNotExists.GenByArgs("instr") + args, err := b.evalArgs(row) + if err != nil { + return d, errors.Trace(err) + } + // INSTR(str, substr) + if args[0].IsNull() || args[1].IsNull() { + return d, nil + } + + var str, substr string + if str, err = args[0].ToString(); err != nil { + return d, errors.Trace(err) + } + if substr, err = args[1].ToString(); err != nil { + return d, errors.Trace(err) + } + + // INSTR performs case **insensitive** search by default, while at least one argument is binary string + // we do case sensitive search. + var caseSensitive bool + if args[0].Kind() == types.KindBytes || args[1].Kind() == types.KindBytes { + caseSensitive = true + } + + var pos, idx int + if caseSensitive { + idx = strings.Index(str, substr) + } else { + idx = strings.Index(strings.ToLower(str), strings.ToLower(substr)) + } + if idx == -1 { + pos = 0 + } else { + if caseSensitive { + pos = idx + 1 + } else { + pos = utf8.RuneCountInString(str[:idx]) + 1 + } + } + d.SetInt64(int64(pos)) + return d, nil } type loadFileFunctionClass struct { diff --git a/expression/builtin_string_test.go b/expression/builtin_string_test.go index 030c3d82ce0ee..98006f143dd6c 100644 --- a/expression/builtin_string_test.go +++ b/expression/builtin_string_test.go @@ -984,6 +984,50 @@ func (s *testEvaluatorSuite) TestLpad(c *C) { } } +func (s *testEvaluatorSuite) TestInstr(c *C) { + defer testleak.AfterTest(c)() + tbl := []struct { + Args []interface{} + Want interface{} + }{ + {[]interface{}{"foobarbar", "bar"}, 4}, + {[]interface{}{"xbar", "foobar"}, 0}, + + {[]interface{}{123456234, 234}, 2}, + {[]interface{}{123456, 567}, 0}, + {[]interface{}{1e10, 1e2}, 1}, + {[]interface{}{1.234, ".234"}, 2}, + {[]interface{}{1.234, ""}, 1}, + {[]interface{}{"", 123}, 0}, + {[]interface{}{"", ""}, 1}, + + {[]interface{}{"中文美好", "美好"}, 3}, + {[]interface{}{"中文美好", "世界"}, 0}, + {[]interface{}{"中文abc", "a"}, 3}, + + {[]interface{}{"live LONG and prosper", "long"}, 6}, + + {[]interface{}{"not BINARY string", "binary"}, 5}, + {[]interface{}{[]byte("BINARY string"), []byte("binary")}, 0}, + {[]interface{}{[]byte("BINARY string"), []byte("BINARY")}, 1}, + {[]interface{}{[]byte("中文abc"), []byte("abc")}, 7}, + + {[]interface{}{"foobar", nil}, nil}, + {[]interface{}{nil, "foobar"}, nil}, + {[]interface{}{nil, nil}, nil}, + } + + Dtbl := tblToDtbl(tbl) + instr := funcs[ast.Instr] + for i, t := range Dtbl { + f, err := instr.getFunction(datumsToConstants(t["Args"]), s.ctx) + c.Assert(err, IsNil) + got, err := f.eval(nil) + c.Assert(err, IsNil) + c.Assert(got, DeepEquals, t["Want"][0], Commentf("[%d]: args: %v", i, t["Args"])) + } +} + func (s *testEvaluatorSuite) TestMakeSet(c *C) { defer testleak.AfterTest(c)() diff --git a/plan/typeinferer.go b/plan/typeinferer.go index 8592754140913..968bd4a07ab3d 100644 --- a/plan/typeinferer.go +++ b/plan/typeinferer.go @@ -380,8 +380,8 @@ func (v *typeInferrer) handleFuncCallExpr(x *ast.FuncCallExpr) { "date_format", "rpad", "lpad", "char_func", "conv", "make_set", "oct", "uuid": tp = types.NewFieldType(mysql.TypeVarString) chs = v.defaultCharset - case "strcmp", "isnull", "bit_length", "char_length", "character_length", "crc32", "timestampdiff", "sign", - "is_ipv6", "ord": + case "strcmp", "isnull", "bit_length", "char_length", "character_length", "crc32", "timestampdiff", + "sign", "is_ipv6", "ord", "instr": tp = types.NewFieldType(mysql.TypeLonglong) case "connection_id": tp = types.NewFieldType(mysql.TypeLonglong) diff --git a/plan/typeinferer_test.go b/plan/typeinferer_test.go index aa7c3e677620d..1960694f95d4f 100644 --- a/plan/typeinferer_test.go +++ b/plan/typeinferer_test.go @@ -227,6 +227,7 @@ func (ts *testTypeInferrerSuite) TestInferType(c *C) { {"char_length('TiDB')", mysql.TypeLonglong, charset.CharsetBin}, {"character_length('TiDB')", mysql.TypeLonglong, charset.CharsetBin}, {"crc32('TiDB')", mysql.TypeLonglong, charset.CharsetBin}, + {"instr('foobarbar', 'bar')", mysql.TypeLonglong, charset.CharsetBin}, {"timestampdiff(MINUTE,'2003-02-01','2003-05-01 12:05:55')", mysql.TypeLonglong, charset.CharsetBin}, {"sign(0)", mysql.TypeLonglong, charset.CharsetBin}, {"sign(null)", mysql.TypeLonglong, charset.CharsetBin},