diff --git a/expression/builtin_json.go b/expression/builtin_json.go index 7fb2aec1e2164..d3a6c39e86e7c 100644 --- a/expression/builtin_json.go +++ b/expression/builtin_json.go @@ -27,6 +27,7 @@ var ( _ functionClass = &jsonTypeFunctionClass{} _ functionClass = &jsonExtractFunctionClass{} _ functionClass = &jsonUnquoteFunctionClass{} + _ functionClass = &jsonQuoteFunctionClass{} _ functionClass = &jsonSetFunctionClass{} _ functionClass = &jsonInsertFunctionClass{} _ functionClass = &jsonReplaceFunctionClass{} @@ -50,6 +51,7 @@ var ( _ functionClass = &jsonLengthFunctionClass{} _ builtinFunc = &builtinJSONTypeSig{} + _ builtinFunc = &builtinJSONQuoteSig{} _ builtinFunc = &builtinJSONUnquoteSig{} _ builtinFunc = &builtinJSONArraySig{} _ builtinFunc = &builtinJSONObjectSig{} @@ -750,8 +752,34 @@ type jsonQuoteFunctionClass struct { baseFunctionClass } +type builtinJSONQuoteSig struct { + baseBuiltinFunc +} + +func (b *builtinJSONQuoteSig) Clone() builtinFunc { + newSig := &builtinJSONQuoteSig{} + newSig.cloneFrom(&b.baseBuiltinFunc) + return newSig +} + func (c *jsonQuoteFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) { - return nil, errFunctionNotExists.GenWithStackByArgs("FUNCTION", "JSON_QUOTE") + if err := c.verifyArgs(args); err != nil { + return nil, errors.Trace(err) + } + bf := newBaseBuiltinFuncWithTp(ctx, args, types.ETString, types.ETJson) + DisableParseJSONFlag4Expr(args[0]) + sig := &builtinJSONQuoteSig{bf} + sig.setPbCode(tipb.ScalarFuncSig_JsonQuoteSig) + return sig, nil +} + +func (b *builtinJSONQuoteSig) evalString(row chunk.Row) (res string, isNull bool, err error) { + var j json.BinaryJSON + j, isNull, err = b.args[0].EvalJSON(b.ctx, row) + if isNull || err != nil { + return "", isNull, errors.Trace(err) + } + return j.Quote(), false, nil } type jsonSearchFunctionClass struct { diff --git a/expression/builtin_json_test.go b/expression/builtin_json_test.go index 8f6888e8c035c..58d7712aa1064 100644 --- a/expression/builtin_json_test.go +++ b/expression/builtin_json_test.go @@ -48,6 +48,35 @@ func (s *testEvaluatorSuite) TestJSONType(c *C) { } } +func (s *testEvaluatorSuite) TestJSONQuote(c *C) { + defer testleak.AfterTest(c)() + fc := funcs[ast.JSONQuote] + tbl := []struct { + Input interface{} + Expected interface{} + }{ + {nil, nil}, + {``, `""`}, + {`""`, `"\"\""`}, + {`a`, `"a"`}, + {`3`, `"3"`}, + {`{"a": "b"}`, `"{\"a\": \"b\"}"`}, + {`{"a": "b"}`, `"{\"a\": \"b\"}"`}, + {`hello,"quoted string",world`, `"hello,\"quoted string\",world"`}, + {`hello,"宽字符",world`, `"hello,\"宽字符\",world"`}, + {`Invalid Json string is OK`, `"Invalid Json string\tis OK"`}, + {`1\u2232\u22322`, `"1\\u2232\\u22322"`}, + } + 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]) + } +} + func (s *testEvaluatorSuite) TestJSONUnquote(c *C) { defer testleak.AfterTest(c)() fc := funcs[ast.JSONUnquote] diff --git a/expression/distsql_builtin.go b/expression/distsql_builtin.go index b20c8364dcc29..e0c5b659c4e54 100644 --- a/expression/distsql_builtin.go +++ b/expression/distsql_builtin.go @@ -414,6 +414,8 @@ func getSignatureByPB(ctx sessionctx.Context, sigCode tipb.ScalarFuncSig, tp *ti case tipb.ScalarFuncSig_JsonTypeSig: f = &builtinJSONTypeSig{base} + case tipb.ScalarFuncSig_JsonQuoteSig: + f = &builtinQuoteSig{base} case tipb.ScalarFuncSig_JsonUnquoteSig: f = &builtinJSONUnquoteSig{base} case tipb.ScalarFuncSig_JsonArraySig: diff --git a/expression/integration_test.go b/expression/integration_test.go index 58cb813f96ff4..bbf019e567a38 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -3371,6 +3371,22 @@ func (s *testIntegrationSuite) TestFuncJSON(c *C) { r = tk.MustQuery(`select json_unquote('hello'), json_unquote('world')`) r.Check(testkit.Rows("hello world")) + r = tk.MustQuery(`select + json_quote(''), + json_quote('""'), + json_quote('a'), + json_quote('3'), + json_quote('{"a": "b"}'), + json_quote('{"a": "b"}'), + json_quote('hello,"quoted string",world'), + json_quote('hello,"宽字符",world'), + json_quote('Invalid Json string is OK'), + json_quote('1\u2232\u22322') + `) + r.Check(testkit.Rows( + `"" "\"\"" "a" "3" "{\"a\": \"b\"}" "{\"a\": \"b\"}" "hello,\"quoted string\",world" "hello,\"宽字符\",world" "Invalid Json string\tis OK" "1u2232u22322"`, + )) + r = tk.MustQuery(`select json_extract(a, '$.a[1]'), json_extract(b, '$.b') from table_json`) r.Check(testkit.Rows("\"2\" true", " ")) diff --git a/types/json/binary_functions.go b/types/json/binary_functions.go index 2ea6ae3d124bd..e36a7d0805d3b 100644 --- a/types/json/binary_functions.go +++ b/types/json/binary_functions.go @@ -19,6 +19,7 @@ import ( "encoding/hex" "fmt" "sort" + "strconv" "unicode/utf8" "unsafe" @@ -54,6 +55,11 @@ func (bj BinaryJSON) Type() string { } } +// Quote is for JSON_QUOTE +func (bj BinaryJSON) Quote() string { + return strconv.Quote(hack.String(bj.GetString())) +} + // Unquote is for JSON_UNQUOTE. func (bj BinaryJSON) Unquote() (string, error) { switch bj.TypeCode {