diff --git a/expression/builtin_json.go b/expression/builtin_json.go index 3869a1be8bcf7..4ef02dd0f92bc 100644 --- a/expression/builtin_json.go +++ b/expression/builtin_json.go @@ -14,12 +14,15 @@ package expression import ( + json2 "encoding/json" + "github.com/pingcap/errors" "github.com/pingcap/parser/ast" "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/types/json" "github.com/pingcap/tidb/util/chunk" + "github.com/pingcap/tidb/util/hack" "github.com/pingcap/tipb/go-tipb" ) @@ -61,6 +64,9 @@ var ( _ builtinFunc = &builtinJSONMergeSig{} _ builtinFunc = &builtinJSONContainsSig{} _ builtinFunc = &builtinJSONLengthSig{} + _ builtinFunc = &builtinJSONValidJSONSig{} + _ builtinFunc = &builtinJSONValidStringSig{} + _ builtinFunc = &builtinJSONValidOthersSig{} ) type jsonTypeFunctionClass struct { @@ -685,7 +691,87 @@ type jsonValidFunctionClass struct { } func (c *jsonValidFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) { - return nil, errFunctionNotExists.GenWithStackByArgs("FUNCTION", "JSON_VALID") + if err := c.verifyArgs(args); err != nil { + return nil, err + } + + var sig builtinFunc + argType := args[0].GetType().EvalType() + switch argType { + case types.ETJson: + bf := newBaseBuiltinFuncWithTp(ctx, args, types.ETInt, types.ETJson) + sig = &builtinJSONValidJSONSig{bf} + sig.setPbCode(tipb.ScalarFuncSig_JsonValidJsonSig) + case types.ETString: + bf := newBaseBuiltinFuncWithTp(ctx, args, types.ETInt, types.ETString) + sig = &builtinJSONValidStringSig{bf} + sig.setPbCode(tipb.ScalarFuncSig_JsonValidStringSig) + default: + bf := newBaseBuiltinFuncWithTp(ctx, args, types.ETInt, argType) + sig = &builtinJSONValidOthersSig{bf} + sig.setPbCode(tipb.ScalarFuncSig_JsonValidOthersSig) + } + return sig, nil +} + +type builtinJSONValidJSONSig struct { + baseBuiltinFunc +} + +func (b *builtinJSONValidJSONSig) Clone() builtinFunc { + newSig := &builtinJSONValidJSONSig{} + newSig.cloneFrom(&b.baseBuiltinFunc) + return newSig +} + +// evalInt evals a builtinJSONValidJSONSig. +// See https://dev.mysql.com/doc/refman/5.7/en/json-attribute-functions.html#function_json-valid +func (b *builtinJSONValidJSONSig) evalInt(row chunk.Row) (res int64, isNull bool, err error) { + _, isNull, err = b.args[0].EvalJSON(b.ctx, row) + return 1, isNull, err +} + +type builtinJSONValidStringSig struct { + baseBuiltinFunc +} + +func (b *builtinJSONValidStringSig) Clone() builtinFunc { + newSig := &builtinJSONValidStringSig{} + newSig.cloneFrom(&b.baseBuiltinFunc) + return newSig +} + +// evalInt evals a builtinJSONValidStringSig. +// See https://dev.mysql.com/doc/refman/5.7/en/json-attribute-functions.html#function_json-valid +func (b *builtinJSONValidStringSig) evalInt(row chunk.Row) (res int64, isNull bool, err error) { + val, isNull, err := b.args[0].EvalString(b.ctx, row) + if err != nil || isNull { + return 0, isNull, err + } + + data := hack.Slice(val) + if json2.Valid(data) { + res = 1 + } else { + res = 0 + } + return res, false, nil +} + +type builtinJSONValidOthersSig struct { + baseBuiltinFunc +} + +func (b *builtinJSONValidOthersSig) Clone() builtinFunc { + newSig := &builtinJSONValidOthersSig{} + newSig.cloneFrom(&b.baseBuiltinFunc) + return newSig +} + +// evalInt evals a builtinJSONValidOthersSig. +// See https://dev.mysql.com/doc/refman/5.7/en/json-attribute-functions.html#function_json-valid +func (b *builtinJSONValidOthersSig) evalInt(row chunk.Row) (res int64, isNull bool, err error) { + return 0, false, nil } type jsonArrayAppendFunctionClass struct { diff --git a/expression/builtin_json_test.go b/expression/builtin_json_test.go index d6e6542fc119b..423ccf0eff3d7 100644 --- a/expression/builtin_json_test.go +++ b/expression/builtin_json_test.go @@ -495,3 +495,34 @@ func (s *testEvaluatorSuite) TestJSONLength(c *C) { } } } + +func (s *testEvaluatorSuite) TestJSONValid(c *C) { + defer testleak.AfterTest(c)() + fc := funcs[ast.JSONValid] + tbl := []struct { + Input interface{} + Expected interface{} + }{ + {`{"a":1}`, 1}, + {`hello`, 0}, + {`"hello"`, 1}, + {`null`, 1}, + {`{}`, 1}, + {`[]`, 1}, + {`2`, 1}, + {`2.5`, 1}, + {`2019-8-19`, 0}, + {`"2019-8-19"`, 1}, + {2, 0}, + {2.5, 0}, + {nil, nil}, + } + dtbl := tblToDtbl(tbl) + for _, t := range dtbl { + f, err := fc.getFunction(s.ctx, s.datumsToConstants(t["Input"])) + c.Assert(err, IsNil) + d, err := evalBuiltinFunc(f, chunk.Row{}) + c.Assert(err, IsNil) + c.Assert(d, testutil.DatumEquals, t["Expected"][0]) + } +} diff --git a/expression/distsql_builtin.go b/expression/distsql_builtin.go index d8a51091ef0ce..0fd80416c7706 100644 --- a/expression/distsql_builtin.go +++ b/expression/distsql_builtin.go @@ -438,7 +438,12 @@ func getSignatureByPB(ctx sessionctx.Context, sigCode tipb.ScalarFuncSig, tp *ti f = &builtinLikeSig{base} case tipb.ScalarFuncSig_JsonLengthSig: f = &builtinJSONLengthSig{base} - + case tipb.ScalarFuncSig_JsonValidJsonSig: + f = &builtinJSONValidJSONSig{base} + case tipb.ScalarFuncSig_JsonValidStringSig: + f = &builtinJSONValidStringSig{base} + case tipb.ScalarFuncSig_JsonValidOthersSig: + f = &builtinJSONValidOthersSig{base} case tipb.ScalarFuncSig_InInt: f = &builtinInIntSig{base} case tipb.ScalarFuncSig_InReal: diff --git a/expression/integration_test.go b/expression/integration_test.go index 4f865aeb1ae13..c03cae5121c80 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -3292,6 +3292,39 @@ func (s *testIntegrationSuite) TestJSONBuiltin(c *C) { tk.MustExec("CREATE TABLE `my_collection` ( `doc` json DEFAULT NULL, `_id` varchar(32) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(doc,'$._id'))) STORED NOT NULL, PRIMARY KEY (`_id`))") _, err := tk.Exec("UPDATE `test`.`my_collection` SET doc=JSON_SET(doc) WHERE (JSON_EXTRACT(doc,'$.name') = 'clare');") c.Assert(err, NotNil) + + r := tk.MustQuery("select json_valid(null);") + r.Check(testkit.Rows("")) + + r = tk.MustQuery(`select json_valid("null");`) + r.Check(testkit.Rows("1")) + + r = tk.MustQuery("select json_valid(0);") + r.Check(testkit.Rows("0")) + + r = tk.MustQuery(`select json_valid("0");`) + r.Check(testkit.Rows("1")) + + r = tk.MustQuery(`select json_valid("hello");`) + r.Check(testkit.Rows("0")) + + r = tk.MustQuery(`select json_valid('"hello"');`) + r.Check(testkit.Rows("1")) + + r = tk.MustQuery(`select json_valid('{"a":1}');`) + r.Check(testkit.Rows("1")) + + r = tk.MustQuery("select json_valid('{}');") + r.Check(testkit.Rows("1")) + + r = tk.MustQuery(`select json_valid('[]');`) + r.Check(testkit.Rows("1")) + + r = tk.MustQuery("select json_valid('2019-8-19');") + r.Check(testkit.Rows("0")) + + r = tk.MustQuery(`select json_valid('"2019-8-19"');`) + r.Check(testkit.Rows("1")) } func (s *testIntegrationSuite) TestTimeLiteral(c *C) { diff --git a/go.mod b/go.mod index 1ee6446877fe0..6ce1e746c1d50 100644 --- a/go.mod +++ b/go.mod @@ -84,3 +84,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect ) + +go 1.13 + +replace github.com/pingcap/tipb => github.com/SunRunAway/tipb v0.0.0-20191015062550-6a2d99cc954a diff --git a/go.sum b/go.sum index 80809675a2552..72b4e51964c36 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/SunRunAway/tipb v0.0.0-20191015054648-a49ece5914c0 h1:AdohlGmxzxLVDKHkvbj7z8WpCnqvnRwqCjr5VSAEeCk= +github.com/SunRunAway/tipb v0.0.0-20191015054648-a49ece5914c0/go.mod h1:myhDmco5Sn/B+RTZYjXbgqmlywhxgM8Gdfy44NfarHw= +github.com/SunRunAway/tipb v0.0.0-20191015062550-6a2d99cc954a h1:1a1ZaqiYNxpINKwF43UORmt/hbKBe4oYAXMvvITRXtk= +github.com/SunRunAway/tipb v0.0.0-20191015062550-6a2d99cc954a/go.mod h1:myhDmco5Sn/B+RTZYjXbgqmlywhxgM8Gdfy44NfarHw= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/apache/thrift v0.0.0-20161221203622-b2a4d4ae21c7 h1:Fv9bK1Q+ly/ROk4aJsVMeuIwPel4bEnD8EPiI91nZMg= github.com/apache/thrift v0.0.0-20161221203622-b2a4d4ae21c7/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=