Skip to content

Commit

Permalink
expression: add builtin function json_merge_patch (pingcap#24711)
Browse files Browse the repository at this point in the history
  • Loading branch information
jianzhiyao authored Jul 10, 2021
1 parent 9419467 commit 372c529
Show file tree
Hide file tree
Showing 6 changed files with 482 additions and 1 deletion.
51 changes: 50 additions & 1 deletion expression/builtin_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,56 @@ type jsonMergePatchFunctionClass struct {
}

func (c *jsonMergePatchFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
return nil, errFunctionNotExists.GenWithStackByArgs("FUNCTION", "JSON_MERGE_PATCH")
if err := c.verifyArgs(args); err != nil {
return nil, err
}
argTps := make([]types.EvalType, 0, len(args))
for range args {
argTps = append(argTps, types.ETJson)
}
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETJson, argTps...)
if err != nil {
return nil, err
}
sig := &builtinJSONMergePatchSig{bf}
sig.setPbCode(tipb.ScalarFuncSig_JsonMergePatchSig)
return sig, nil
}

type builtinJSONMergePatchSig struct {
baseBuiltinFunc
}

func (b *builtinJSONMergePatchSig) Clone() builtinFunc {
newSig := &builtinJSONMergePatchSig{}
newSig.cloneFrom(&b.baseBuiltinFunc)
return newSig
}

func (b *builtinJSONMergePatchSig) evalJSON(row chunk.Row) (res json.BinaryJSON, isNull bool, err error) {
values := make([]*json.BinaryJSON, 0, len(b.args))
for _, arg := range b.args {
var value json.BinaryJSON
value, isNull, err = arg.EvalJSON(b.ctx, row)
if err != nil {
return
}
if isNull {
values = append(values, nil)
} else {
values = append(values, &value)
}
}
tmpRes, err := json.MergePatchBinary(values)
if err != nil {
return
}
if tmpRes != nil {
res = *tmpRes
} else {
isNull = true
}
return res, isNull, nil
}

type jsonMergePreserveFunctionClass struct {
Expand Down
91 changes: 91 additions & 0 deletions expression/builtin_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1076,3 +1076,94 @@ func (s *testEvaluatorSuite) TestJSONPretty(c *C) {
}
}
}

func (s *testEvaluatorSuite) TestJSONMergePatch(c *C) {
fc := funcs[ast.JSONMergePatch]
tbl := []struct {
input []interface{}
expected interface{}
success bool
}{
// RFC 7396 document: https://datatracker.ietf.org/doc/html/rfc7396
// RFC 7396 Example Test Cases
{[]interface{}{`{"a":"b"}`, `{"a":"c"}`}, `{"a": "c"}`, true},
{[]interface{}{`{"a":"b"}`, `{"b":"c"}`}, `{"a": "b","b": "c"}`, true},
{[]interface{}{`{"a":"b"}`, `{"a":null}`}, `{}`, true},
{[]interface{}{`{"a":"b", "b":"c"}`, `{"a":null}`}, `{"b": "c"}`, true},
{[]interface{}{`{"a":["b"]}`, `{"a":"c"}`}, `{"a": "c"}`, true},
{[]interface{}{`{"a":"c"}`, `{"a":["b"]}`}, `{"a": ["b"]}`, true},
{[]interface{}{`{"a":{"b":"c"}}`, `{"a":{"b":"d","c":null}}`}, `{"a": {"b": "d"}}`, true},
{[]interface{}{`{"a":[{"b":"c"}]}`, `{"a": [1]}`}, `{"a": [1]}`, true},
{[]interface{}{`["a","b"]`, `["c","d"]`}, `["c", "d"]`, true},
{[]interface{}{`{"a":"b"}`, `["c"]`}, `["c"]`, true},
{[]interface{}{`{"a":"foo"}`, `null`}, `null`, true},
{[]interface{}{`{"a":"foo"}`, `"bar"`}, `"bar"`, true},
{[]interface{}{`{"e":null}`, `{"a":1}`}, `{"e": null,"a": 1}`, true},
{[]interface{}{`[1,2]`, `{"a":"b","c":null}`}, `{"a":"b"}`, true},
{[]interface{}{`{}`, `{"a":{"bb":{"ccc":null}}}`}, `{"a":{"bb": {}}}`, true},
// RFC 7396 Example Document
{[]interface{}{`{"title":"Goodbye!","author":{"givenName":"John","familyName":"Doe"},"tags":["example","sample"],"content":"This will be unchanged"}`, `{"title":"Hello!","phoneNumber":"+01-123-456-7890","author":{"familyName":null},"tags":["example"]}`}, `{"title":"Hello!","author":{"givenName":"John"},"tags":["example"],"content":"This will be unchanged","phoneNumber":"+01-123-456-7890"}`, true},

// From mysql Example Test Cases
{[]interface{}{nil, `null`, `[1,2,3]`, `{"a":1}`}, `{"a": 1}`, true},
{[]interface{}{`null`, nil, `[1,2,3]`, `{"a":1}`}, `{"a": 1}`, true},
{[]interface{}{`null`, `[1,2,3]`, nil, `{"a":1}`}, nil, true},
{[]interface{}{`null`, `[1,2,3]`, `{"a":1}`, nil}, nil, true},

{[]interface{}{nil, `null`, `{"a":1}`, `[1,2,3]`}, `[1,2,3]`, true},
{[]interface{}{`null`, nil, `{"a":1}`, `[1,2,3]`}, `[1,2,3]`, true},
{[]interface{}{`null`, `{"a":1}`, nil, `[1,2,3]`}, `[1,2,3]`, true},
{[]interface{}{`null`, `{"a":1}`, `[1,2,3]`, nil}, nil, true},

{[]interface{}{nil, `null`, `{"a":1}`, `true`}, `true`, true},
{[]interface{}{`null`, nil, `{"a":1}`, `true`}, `true`, true},
{[]interface{}{`null`, `{"a":1}`, nil, `true`}, `true`, true},
{[]interface{}{`null`, `{"a":1}`, `true`, nil}, nil, true},

// non-object last item
{[]interface{}{"true", "false", "[]", "{}", "null"}, "null", true},
{[]interface{}{"false", "[]", "{}", "null", "true"}, "true", true},
{[]interface{}{"true", "[]", "{}", "null", "false"}, "false", true},
{[]interface{}{"true", "false", "{}", "null", "[]"}, "[]", true},
{[]interface{}{"true", "false", "{}", "null", "1"}, "1", true},
{[]interface{}{"true", "false", "{}", "null", "1.8"}, "1.8", true},
{[]interface{}{"true", "false", "{}", "null", `"112"`}, `"112"`, true},

{[]interface{}{`{"a":"foo"}`, nil}, nil, true},
{[]interface{}{nil, `{"a":"foo"}`}, nil, true},
{[]interface{}{`{"a":"foo"}`, `false`}, `false`, true},
{[]interface{}{`{"a":"foo"}`, `123`}, `123`, true},
{[]interface{}{`{"a":"foo"}`, `123.1`}, `123.1`, true},
{[]interface{}{`{"a":"foo"}`, `[1,2,3]`}, `[1,2,3]`, true},
{[]interface{}{`null`, `{"a":1}`}, `{"a":1}`, true},
{[]interface{}{`{"a":1}`, `null`}, `null`, true},
{[]interface{}{`{"a":"foo"}`, `{"a":null}`, `{"b":"123"}`, `{"c":1}`}, `{"b":"123","c":1}`, true},
{[]interface{}{`{"a":"foo"}`, `{"a":null}`, `{"c":1}`}, `{"c":1}`, true},
{[]interface{}{`{"a":"foo"}`, `{"a":null}`, `true`}, `true`, true},
{[]interface{}{`{"a":"foo"}`, `{"d":1}`, `{"a":{"bb":{"ccc":null}}}`}, `{"a":{"bb":{}},"d":1}`, true},

// Invalid json text
{[]interface{}{`{"a":1}`, `[1]}`}, nil, false},
{[]interface{}{`{{"a":1}`, `[1]`, `null`}, nil, false},
{[]interface{}{`{"a":1}`, `jjj`, `null`}, nil, false},
}
for _, t := range tbl {
args := types.MakeDatums(t.input...)
f, err := fc.getFunction(s.ctx, s.datumsToConstants(args))
c.Assert(err, IsNil)
d, err := evalBuiltinFunc(f, chunk.Row{})
if t.success {
c.Assert(err, IsNil)

if t.expected == nil {
c.Assert(d.IsNull(), IsTrue)
} else {
j, e := json.ParseBinaryFromString(t.expected.(string))
c.Assert(e, IsNil)
c.Assert(d.GetMysqlJSON().String(), Equals, j.String())
}
} else {
c.Assert(err, NotNil)
}
}
}
48 changes: 48 additions & 0 deletions expression/builtin_json_vec.go
Original file line number Diff line number Diff line change
Expand Up @@ -1218,3 +1218,51 @@ func (b *builtinJSONSPrettySig) vecEvalString(input *chunk.Chunk, result *chunk.
}
return nil
}

func (b *builtinJSONMergePatchSig) vectorized() bool {
return true
}

func (b *builtinJSONMergePatchSig) vecEvalJSON(input *chunk.Chunk, result *chunk.Column) error {
nr := input.NumRows()
argBuffers := make([]*chunk.Column, len(b.args))
var err error
for i, arg := range b.args {
if argBuffers[i], err = b.bufAllocator.get(types.ETJson, nr); err != nil {
return err
}
defer func(buf *chunk.Column) {
b.bufAllocator.put(buf)
}(argBuffers[i])

if err := arg.VecEvalJSON(b.ctx, input, argBuffers[i]); err != nil {
return err
}
}

result.ReserveJSON(nr)
jsonValue := make([]*json.BinaryJSON, 0, len(b.args))
for i := 0; i < nr; i++ {
jsonValue = jsonValue[:0]
for j := 0; j < len(b.args); j++ {
if argBuffers[j].IsNull(i) {
jsonValue = append(jsonValue, nil)
} else {
v := argBuffers[j].GetJSON(i)
jsonValue = append(jsonValue, &v)
}
}

tmpJSON, e := json.MergePatchBinary(jsonValue)
if e != nil {
return e
}
if tmpJSON == nil {
result.AppendNull()
} else {
result.AppendJSON(*tmpJSON)
}
}

return nil
}
Loading

0 comments on commit 372c529

Please sign in to comment.