diff --git a/go/vt/vtgate/evalengine/compiler.go b/go/vt/vtgate/evalengine/compiler.go index c69df3a300f..1718fb4b043 100644 --- a/go/vt/vtgate/evalengine/compiler.go +++ b/go/vt/vtgate/evalengine/compiler.go @@ -585,6 +585,8 @@ func (c *compiler) compileParseJSON(fn string, doct ctype, offset int) (ctype, e case sqltypes.TypeJSON: case sqltypes.VarChar, sqltypes.VarBinary: c.asm.Parse_j(offset) + case sqltypes.Null: + return ctype{Type: sqltypes.Null, Flag: flagNull | flagNullable, Col: collationNull}, nil default: return ctype{}, errJSONType(fn) } diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go index b9777703ce2..5b945db81cf 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -2319,6 +2319,15 @@ func (asm *assembler) Fn_JSON_KEYS(jp *json.Path) { return 1 }, "FN JSON_KEYS (SP-1)") } else { + if jp.ContainsWildcards() { + asm.emit(func(env *ExpressionEnv) int { + env.vm.err = errInvalidPathForTransform + return 1 + }, "FN JSON_KEYS (SP-1), %q", jp.String()) + + return + } + asm.emit(func(env *ExpressionEnv) int { doc := env.vm.stack[env.vm.sp-1] if doc == nil { diff --git a/go/vt/vtgate/evalengine/fn_json.go b/go/vt/vtgate/evalengine/fn_json.go index dc419856057..1a65624d682 100644 --- a/go/vt/vtgate/evalengine/fn_json.go +++ b/go/vt/vtgate/evalengine/fn_json.go @@ -134,16 +134,9 @@ func (call *builtinJSONExtract) compile(c *compiler) (ctype, error) { nullable := doct.nullable() skip := c.compileNullCheck1(doct) - // TODO: `*compiler.compileParseJSON` should handle `sqltypes.Null`` properly but - // we'll handle it here until all call sites are fixed. - var jt ctype - if doct.Type != sqltypes.Null { - jt, err = c.compileParseJSON("JSON_EXTRACT", doct, 1) - if err != nil { - return ctype{}, err - } - } else { - jt = ctype{Type: sqltypes.Null, Flag: flagNull | flagNullable, Col: collationNull} + jt, err := c.compileParseJSON("JSON_EXTRACT", doct, 1) + if err != nil { + return ctype{}, err } staticPaths := make([]staticPath, 0, len(call.Arguments[1:])) @@ -232,16 +225,9 @@ func (call *builtinJSONRemove) compile(c *compiler) (ctype, error) { nullable := doct.nullable() skip := c.compileNullCheck1(doct) - // TODO: `*compiler.compileParseJSON` should handle `sqltypes.Null`` properly but - // we'll handle it here until all call sites are fixed. - var jt ctype - if doct.Type != sqltypes.Null { - jt, err = c.compileParseJSON("JSON_REMOVE", doct, 1) - if err != nil { - return ctype{}, err - } - } else { - jt = ctype{Type: sqltypes.Null, Flag: flagNull | flagNullable, Col: collationNull} + jt, err := c.compileParseJSON("JSON_REMOVE", doct, 1) + if err != nil { + return ctype{}, err } staticPaths := make([]staticPath, 0, len(call.Arguments[1:])) @@ -513,6 +499,13 @@ func (call *builtinJSONContainsPath) compile(c *compiler) (ctype, error) { return ctype{}, c.unsupported(call) } + skip := c.compileNullCheck1(doct) + + _, err = c.compileParseJSON("JSON_CONTAINS_PATH", doct, 1) + if err != nil { + return ctype{}, err + } + match, err := c.jsonExtractOneOrAll("JSON_CONTAINS_PATH", call.Arguments[1]) if err != nil { return ctype{}, err @@ -528,12 +521,10 @@ func (call *builtinJSONContainsPath) compile(c *compiler) (ctype, error) { paths = append(paths, jp) } - _, err = c.compileParseJSON("JSON_CONTAINS_PATH", doct, 1) - if err != nil { - return ctype{}, err - } - c.asm.Fn_JSON_CONTAINS_PATH(match, paths) + + c.asm.jumpDestination(skip) + return ctype{Type: sqltypes.Int64, Col: collationNumeric, Flag: flagIsBoolean | flagNullable}, nil } @@ -624,6 +615,8 @@ func (call *builtinJSONKeys) compile(c *compiler) (ctype, error) { return ctype{}, err } + skip := c.compileNullCheck1(doc) + _, err = c.compileParseJSON("JSON_KEYS", doc, 1) if err != nil { return ctype{}, err @@ -635,11 +628,11 @@ func (call *builtinJSONKeys) compile(c *compiler) (ctype, error) { if err != nil { return ctype{}, err } - if jp.ContainsWildcards() { - return ctype{}, errInvalidPathForTransform - } } c.asm.Fn_JSON_KEYS(jp) + + c.asm.jumpDestination(skip) + return ctype{Type: sqltypes.TypeJSON, Flag: flagNullable, Col: collationJSON}, nil } diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go index 17b086e08e3..0bc991fa933 100644 --- a/go/vt/vtgate/evalengine/testcases/cases.go +++ b/go/vt/vtgate/evalengine/testcases/cases.go @@ -34,6 +34,7 @@ var Cases = []TestCase{ {Run: FnJSONExtract}, {Run: FnJSONRemove}, {Run: FnJSONContainsPath}, + {Run: FnJSONUnquote}, {Run: JSONArray}, {Run: JSONObject}, {Run: CharsetConversionOperators}, @@ -181,10 +182,10 @@ var Cases = []TestCase{ func FnJSONKeys(yield Query) { for _, obj := range inputJSONObjects { - yield(fmt.Sprintf("JSON_KEYS('%s')", obj), nil, false) + yield(fmt.Sprintf("JSON_KEYS(%s)", obj), nil, false) for _, path1 := range inputJSONPaths { - yield(fmt.Sprintf("JSON_KEYS('%s', '%s')", obj, path1), nil, false) + yield(fmt.Sprintf("JSON_KEYS(%s, '%s')", obj, path1), nil, false) } } } @@ -192,10 +193,10 @@ func FnJSONKeys(yield Query) { func FnJSONExtract(yield Query) { for _, obj := range inputJSONObjects { for _, path1 := range inputJSONPaths { - yield(fmt.Sprintf("JSON_EXTRACT('%s', '%s')", obj, path1), nil, false) + yield(fmt.Sprintf("JSON_EXTRACT(%s, '%s')", obj, path1), nil, false) for _, path2 := range inputJSONPaths { - yield(fmt.Sprintf("JSON_EXTRACT('%s', '%s', '%s')", obj, path1, path2), nil, false) + yield(fmt.Sprintf("JSON_EXTRACT(%s, '%s', '%s')", obj, path1, path2), nil, false) } } } @@ -236,7 +237,7 @@ func FnJSONExtract(yield Query) { func FnJSONRemove(yield Query) { for _, obj := range inputJSONObjects { for _, path1 := range inputJSONPaths { - yield(fmt.Sprintf("JSON_REMOVE('%s', '%s')", obj, path1), nil, false) + yield(fmt.Sprintf("JSON_REMOVE(%s, '%s')", obj, path1), nil, false) } } } @@ -244,17 +245,21 @@ func FnJSONRemove(yield Query) { func FnJSONContainsPath(yield Query) { for _, obj := range inputJSONObjects { for _, path1 := range inputJSONPaths { - yield(fmt.Sprintf("JSON_CONTAINS_PATH('%s', 'one', '%s')", obj, path1), nil, false) - yield(fmt.Sprintf("JSON_CONTAINS_PATH('%s', 'all', '%s')", obj, path1), nil, false) + yield(fmt.Sprintf("JSON_CONTAINS_PATH(%s, 'one', '%s')", obj, path1), nil, false) + yield(fmt.Sprintf("JSON_CONTAINS_PATH(%s, 'all', '%s')", obj, path1), nil, false) for _, path2 := range inputJSONPaths { - yield(fmt.Sprintf("JSON_CONTAINS_PATH('%s', 'one', '%s', '%s')", obj, path1, path2), nil, false) - yield(fmt.Sprintf("JSON_CONTAINS_PATH('%s', 'all', '%s', '%s')", obj, path1, path2), nil, false) + yield(fmt.Sprintf("JSON_CONTAINS_PATH(%s, 'one', '%s', '%s')", obj, path1, path2), nil, false) + yield(fmt.Sprintf("JSON_CONTAINS_PATH(%s, 'all', '%s', '%s')", obj, path1, path2), nil, false) } } } } +func FnJSONUnquote(yield Query) { + yield("JSON_UNQUOTE(NULL)", nil, false) +} + func JSONArray(yield Query) { for _, a := range inputJSONPrimitives { yield(fmt.Sprintf("JSON_ARRAY(%s)", a), nil, false) diff --git a/go/vt/vtgate/evalengine/testcases/inputs.go b/go/vt/vtgate/evalengine/testcases/inputs.go index 4c65cc5002f..281eebb4fe0 100644 --- a/go/vt/vtgate/evalengine/testcases/inputs.go +++ b/go/vt/vtgate/evalengine/testcases/inputs.go @@ -24,13 +24,14 @@ import ( ) var inputJSONObjects = []string{ - `[ { "a": 1 }, { "a": 2 } ]`, - `{ "a" : "foo", "b" : [ true, { "c" : 123, "c" : 456 } ] }`, - `{ "a" : "foo", "b" : [ true, { "c" : "123" } ] }`, - `{ "a" : "foo", "b" : [ true, { "c" : 123 } ] }`, - `{"a": 1, "b": 2, "c": {"d": 4}}`, - `["a", {"b": [true, false]}, [10, 20]]`, - `[10, 20, [30, 40]]`, + `'[ { "a": 1 }, { "a": 2 } ]'`, + `'{ "a" : "foo", "b" : [ true, { "c" : 123, "c" : 456 } ] }'`, + `'{ "a" : "foo", "b" : [ true, { "c" : "123" } ] }'`, + `'{ "a" : "foo", "b" : [ true, { "c" : 123 } ] }'`, + `'{"a": 1, "b": 2, "c": {"d": 4}}'`, + `'["a", {"b": [true, false]}, [10, 20]]'`, + `'[10, 20, [30, 40]]'`, + `NULL`, } var inputJSONPaths = []string{