Skip to content

Commit

Permalink
expression: add builtin function json_valid (#13138)
Browse files Browse the repository at this point in the history
  • Loading branch information
Reminiscent authored and sre-bot committed Nov 5, 2019
1 parent 2b13e54 commit 173cede
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 4 deletions.
87 changes: 86 additions & 1 deletion expression/builtin_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package expression

import (
json2 "encoding/json"
"strings"

"github.com/pingcap/errors"
Expand All @@ -22,6 +23,7 @@ import (
"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/tidb/util/stringutil"
"github.com/pingcap/tipb/go-tipb"
)
Expand Down Expand Up @@ -71,6 +73,9 @@ var (
_ builtinFunc = &builtinJSONKeysSig{}
_ builtinFunc = &builtinJSONKeys2ArgsSig{}
_ builtinFunc = &builtinJSONLengthSig{}
_ builtinFunc = &builtinJSONValidJSONSig{}
_ builtinFunc = &builtinJSONValidStringSig{}
_ builtinFunc = &builtinJSONValidOthersSig{}
)

type jsonTypeFunctionClass struct {
Expand Down Expand Up @@ -715,7 +720,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 {
Expand Down
31 changes: 31 additions & 0 deletions expression/builtin_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -865,3 +865,34 @@ func (s *testEvaluatorSuite) TestJSONSearch(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])
}
}
6 changes: 6 additions & 0 deletions expression/distsql_builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,12 @@ func getSignatureByPB(ctx sessionctx.Context, sigCode tipb.ScalarFuncSig, tp *ti
f = &builtinJSONDepthSig{base}
case tipb.ScalarFuncSig_JsonSearchSig:
f = &builtinJSONSearchSig{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}
Expand Down
33 changes: 33 additions & 0 deletions expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3574,6 +3574,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("<nil>"))

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) {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ require (
github.com/pingcap/parser v0.0.0-20191011160321-0c4055ef2c1d
github.com/pingcap/pd v1.1.0-beta.0.20191018040858-0d9d9d67d029
github.com/pingcap/tidb-tools v2.1.3-0.20190321065848-1e8b48f5c168+incompatible
github.com/pingcap/tipb v0.0.0-20190428032612-535e1abaa330
github.com/pingcap/tipb v0.0.0-20191101114505-cbd0e985c780
github.com/prometheus/client_golang v0.9.0
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910
github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ github.com/pingcap/pd v1.1.0-beta.0.20191018040858-0d9d9d67d029 h1:nJp6nnW70cGtm
github.com/pingcap/pd v1.1.0-beta.0.20191018040858-0d9d9d67d029/go.mod h1:+F+Zp4BoMM7TbDKCeosXO+X9A+IWaw/T3/gRBo1sr6Q=
github.com/pingcap/tidb-tools v2.1.3-0.20190321065848-1e8b48f5c168+incompatible h1:MkWCxgZpJBgY2f4HtwWMMFzSBb3+JPzeJgF3VrXE/bU=
github.com/pingcap/tidb-tools v2.1.3-0.20190321065848-1e8b48f5c168+incompatible/go.mod h1:XGdcy9+yqlDSEMTpOXnwf3hiTeqrV6MN/u1se9N8yIM=
github.com/pingcap/tipb v0.0.0-20190428032612-535e1abaa330 h1:rRMLMjIMFulCX9sGKZ1hoov/iROMsKyC8Snc02nSukw=
github.com/pingcap/tipb v0.0.0-20190428032612-535e1abaa330/go.mod h1:RtkHW8WbcNxj8lsbzjaILci01CtYnYbIkQhjyZWrWVI=
github.com/pingcap/tipb v0.0.0-20191101114505-cbd0e985c780 h1:SvFkjLhS/ou97Ey60r8Fq3ZF4wq6wuveWoiLtWLGpek=
github.com/pingcap/tipb v0.0.0-20191101114505-cbd0e985c780/go.mod h1:RtkHW8WbcNxj8lsbzjaILci01CtYnYbIkQhjyZWrWVI=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down

0 comments on commit 173cede

Please sign in to comment.