From 847efe4c43f6f28ff74286d3db629db2490374ec Mon Sep 17 00:00:00 2001 From: Overbool Date: Sat, 6 Oct 2018 22:29:14 +0800 Subject: [PATCH 1/2] builtin: add json quote --- expression/builtin_json.go | 30 +++++++++++++++++++++++++++++- expression/builtin_json_test.go | 29 +++++++++++++++++++++++++++++ expression/distsql_builtin.go | 2 ++ types/json/binary_functions.go | 5 +++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/expression/builtin_json.go b/expression/builtin_json.go index 4f5486e9b1af3..4f1031c0ea156 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{} @@ -735,8 +737,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) + args[0].GetType().Flag &= ^mysql.ParseToJSONFlag + 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 7b78081803a7e..79aac2e258a02 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/types/json/binary_functions.go b/types/json/binary_functions.go index 93b4b6221f71b..692ab190eb75b 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,10 @@ func (bj BinaryJSON) Type() string { } } +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 { From 0457ac9064a25a23a68f2be6f7eff0488fbcc76c Mon Sep 17 00:00:00 2001 From: Overbool Date: Sat, 6 Oct 2018 22:56:43 +0800 Subject: [PATCH 2/2] builtin: add json quote integration test --- expression/builtin_json.go | 2 +- expression/integration_test.go | 16 ++++++++++++++++ types/json/binary_functions.go | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/expression/builtin_json.go b/expression/builtin_json.go index 4f1031c0ea156..ab379f7f70708 100644 --- a/expression/builtin_json.go +++ b/expression/builtin_json.go @@ -752,7 +752,7 @@ func (c *jsonQuoteFunctionClass) getFunction(ctx sessionctx.Context, args []Expr return nil, errors.Trace(err) } bf := newBaseBuiltinFuncWithTp(ctx, args, types.ETString, types.ETJson) - args[0].GetType().Flag &= ^mysql.ParseToJSONFlag + DisableParseJSONFlag4Expr(args[0]) sig := &builtinJSONQuoteSig{bf} sig.setPbCode(tipb.ScalarFuncSig_JsonQuoteSig) return sig, nil diff --git a/expression/integration_test.go b/expression/integration_test.go index 5e5f73d7770f9..540f831fc3bed 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -3333,6 +3333,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 692ab190eb75b..77fafea71c26a 100644 --- a/types/json/binary_functions.go +++ b/types/json/binary_functions.go @@ -55,6 +55,7 @@ func (bj BinaryJSON) Type() string { } } +// Quote is for JSON_QUOTE func (bj BinaryJSON) Quote() string { return strconv.Quote(hack.String(bj.GetString())) }