From 44dd0f14bfdc9f1e15e2d9e06b35026645b7b324 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Wed, 21 Jun 2023 16:12:39 +0900 Subject: [PATCH v2 3/6] SQL JSON functions This Patch introduces three SQL standard JSON functions: JSON() JSON_SCALAR() JSON_SERIALIZE() JSON() produces json values from text, bytea, json or jsonb values, and has facilitites for handling duplicate keys. JSON_SCALAR() produces a json value from any scalar sql value, including json and jsonb. JSON_SERIALIZE() produces text or bytea from input which containis or represents json or jsonb; For the most part these functions don't add any significant new capabilities, but they will be of use to users wanting standard compliant JSON handling. Nikita Glukhov Reviewers have included (in no particular order) Andres Freund, Alexander Korotkov, Pavel Stehule, Andrew Alsup, Erik Rijkers, Zihong Yu, Himanshu Upadhyaya, Daniel Gustafsson, Justin Pryzby. Discussion: https://postgr.es/m/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru Discussion: https://postgr.es/m/20220616233130.rparivafipt6doj3@alap3.anarazel.de Discussion: https://postgr.es/m/abd9b83b-aa66-f230-3d6d-734817f0995d%40postgresql.org --- doc/src/sgml/func.sgml | 71 +++++ src/backend/executor/execExpr.c | 45 +++ src/backend/executor/execExprInterp.c | 42 ++- src/backend/nodes/nodeFuncs.c | 30 ++ src/backend/parser/gram.y | 65 ++++- src/backend/parser/parse_expr.c | 201 +++++++++++++- src/backend/parser/parse_target.c | 9 + src/backend/utils/adt/format_type.c | 4 + src/backend/utils/adt/json.c | 44 ++- src/backend/utils/adt/jsonb.c | 76 +++--- src/backend/utils/adt/ruleutils.c | 14 +- src/include/nodes/parsenodes.h | 48 ++++ src/include/nodes/primnodes.h | 5 +- src/include/parser/kwlist.h | 4 +- src/include/utils/json.h | 19 ++ src/include/utils/jsonb.h | 21 ++ src/test/regress/expected/sqljson.out | 376 ++++++++++++++++++++++++++ src/test/regress/sql/sqljson.sql | 93 +++++++ src/tools/pgindent/typedefs.list | 1 + 19 files changed, 1089 insertions(+), 79 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 5a47ce4343..8de8f527a5 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -16001,6 +16001,77 @@ table2-mapping {"a": "1", "b": "2"} + + + + json constructor + json ( + expression + FORMAT JSON ENCODING UTF8 + { WITH | WITHOUT } UNIQUE KEYS + RETURNING json_data_type ) + + + The expression can be any text type or a + bytea in UTF8 encoding. If the + expression is NULL, an + SQL null value is returned. + If WITH UNIQUE is specified, the + expression must not contain any duplicate + object keys. + + + json('{"a":123, "b":[true,"foo"], "a":"bar"}') + {"a":123, "b":[true,"foo"], "a":"bar"} + + + json('{"a":123,"b":[true,"foo"],"a":"bar"}' returning jsonb) + {"a": "bar", "b": [true, "foo"]} + + + + + + + json_scalar + json_scalar (expression) + RETURNING json_data_type ) + + + Returns a JSON scalar value representing + expression. + If the input is NULL, an SQL NULL is returned. If the input is a number + or a boolean value, a corresponding JSON number or boolean value is + returned. For any other value a JSON string is returned. + + + json_scalar(123.45) + 123.45 + + + json_scalar(CURRENT_TIMESTAMP) + "2022-05-10T10:51:04.62128-04:00" + + + + + + json_serialize ( + expression FORMAT JSON ENCODING UTF8 + RETURNING data_type FORMAT JSON ENCODING UTF8 ) + + + Transforms an SQL/JSON value into a character or binary string. The + expression can be of any JSON type, any + character string type, or bytea in UTF8 encoding. + The returned type can be any character string type or + bytea. The default is text. + + + json_serialize('{ "a" : 1 } ' RETURNING bytea) + \x7b20226122203a2031207d20 + + diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 21f7796e63..95d9fbbc7c 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -48,6 +48,8 @@ #include "utils/array.h" #include "utils/builtins.h" #include "utils/datum.h" +#include "utils/json.h" +#include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/typcache.h" @@ -2313,6 +2315,12 @@ ExecInitExprRec(Expr *node, ExprState *state, { ExecInitExprRec(ctor->func, state, resv, resnull); } + else if ((ctor->type == JSCTOR_JSON_PARSE && !ctor->unique) || + ctor->type == JSCTOR_JSON_SERIALIZE) + { + /* Use the value of the first argument as a result */ + ExecInitExprRec(linitial(args), state, resv, resnull); + } else { JsonConstructorExprState *jcstate; @@ -2351,6 +2359,43 @@ ExecInitExprRec(Expr *node, ExprState *state, argno++; } + /* prepare type cache for datum_to_json[b]() */ + if (ctor->type == JSCTOR_JSON_SCALAR) + { + bool is_jsonb = + ctor->returning->format->format_type == JS_FORMAT_JSONB; + + jcstate->arg_type_cache = + palloc(sizeof(*jcstate->arg_type_cache) * nargs); + + for (int i = 0; i < nargs; i++) + { + int category; + Oid outfuncid; + Oid typid = jcstate->arg_types[i]; + + if (is_jsonb) + { + JsonbTypeCategory jbcat; + + jsonb_categorize_type(typid, &jbcat, &outfuncid); + + category = (int) jbcat; + } + else + { + JsonTypeCategory jscat; + + json_categorize_type(typid, &jscat, &outfuncid); + + category = (int) jscat; + } + + jcstate->arg_type_cache[i].outfuncid = outfuncid; + jcstate->arg_type_cache[i].category = category; + } + } + ExprEvalPushStep(state, &scratch); } diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 851946a927..b83dd1c666 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -3992,7 +3992,7 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, jcstate->arg_values, jcstate->arg_nulls, jcstate->arg_types, - jcstate->constructor->absent_on_null); + ctor->absent_on_null); else if (ctor->type == JSCTOR_JSON_OBJECT) res = (is_jsonb ? jsonb_build_object_worker : @@ -4002,6 +4002,46 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, jcstate->arg_types, jcstate->constructor->absent_on_null, jcstate->constructor->unique); + else if (ctor->type == JSCTOR_JSON_SCALAR) + { + if (jcstate->arg_nulls[0]) + { + res = (Datum) 0; + isnull = true; + } + else + { + Datum value = jcstate->arg_values[0]; + int category = jcstate->arg_type_cache[0].category; + Oid outfuncid = jcstate->arg_type_cache[0].outfuncid; + + if (is_jsonb) + res = to_jsonb_worker(value, category, outfuncid); + else + res = to_json_worker(value, category, outfuncid); + } + } + else if (ctor->type == JSCTOR_JSON_PARSE) + { + if (jcstate->arg_nulls[0]) + { + res = (Datum) 0; + isnull = true; + } + else + { + Datum value = jcstate->arg_values[0]; + text *js = DatumGetTextP(value); + + if (is_jsonb) + res = jsonb_from_text(js, true); + else + { + (void) json_validate(js, true, true); + res = value; + } + } + } else elog(ERROR, "invalid JsonConstructorExpr type %d", ctor->type); diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index c41e6bb984..dda964bd19 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3901,6 +3901,36 @@ raw_expression_tree_walker_impl(Node *node, return true; } break; + case T_JsonParseExpr: + { + JsonParseExpr *jpe = (JsonParseExpr *) node; + + if (WALK(jpe->expr)) + return true; + if (WALK(jpe->output)) + return true; + } + break; + case T_JsonScalarExpr: + { + JsonScalarExpr *jse = (JsonScalarExpr *) node; + + if (WALK(jse->expr)) + return true; + if (WALK(jse->output)) + return true; + } + break; + case T_JsonSerializeExpr: + { + JsonSerializeExpr *jse = (JsonSerializeExpr *) node; + + if (WALK(jse->expr)) + return true; + if (WALK(jse->output)) + return true; + } + break; case T_JsonConstructorExpr: { JsonConstructorExpr *ctor = (JsonConstructorExpr *) node; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 39ab7eac0d..8f239de65c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -566,7 +566,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type copy_options %type Typename SimpleTypename ConstTypename - GenericType Numeric opt_float + GenericType Numeric opt_float JsonType Character ConstCharacter CharacterWithLength CharacterWithoutLength ConstDatetime ConstInterval @@ -648,6 +648,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type json_format_clause_opt json_value_expr + json_returning_clause_opt json_output_clause_opt json_name_and_value json_aggregate_func @@ -726,6 +727,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG + JSON_SCALAR JSON_SERIALIZE KEY KEYS @@ -13984,6 +13986,7 @@ SimpleTypename: $$->typmods = list_make2(makeIntConst(INTERVAL_FULL_RANGE, -1), makeIntConst($3, @3)); } + | JsonType { $$ = $1; } ; /* We have a separate ConstTypename to allow defaulting fixed-length @@ -14002,6 +14005,7 @@ ConstTypename: | ConstBit { $$ = $1; } | ConstCharacter { $$ = $1; } | ConstDatetime { $$ = $1; } + | JsonType { $$ = $1; } ; /* @@ -14370,6 +14374,13 @@ interval_second: } ; +JsonType: + JSON + { + $$ = SystemTypeName("json"); + $$->location = @1; + } + ; /***************************************************************************** * @@ -15628,7 +15639,37 @@ func_expr_common_subexpr: n->location = @1; $$ = (Node *) n; } - ; + | JSON '(' json_value_expr json_key_uniqueness_constraint_opt + json_returning_clause_opt ')' + { + JsonParseExpr *n = makeNode(JsonParseExpr); + + n->expr = (JsonValueExpr *) $3; + n->unique_keys = $4; + n->output = (JsonOutput *) $5; + n->location = @1; + $$ = (Node *) n; + } + | JSON_SCALAR '(' a_expr json_returning_clause_opt ')' + { + JsonScalarExpr *n = makeNode(JsonScalarExpr); + + n->expr = (Expr *) $3; + n->output = (JsonOutput *) $4; + n->location = @1; + $$ = (Node *) n; + } + | JSON_SERIALIZE '(' json_value_expr json_output_clause_opt ')' + { + JsonSerializeExpr *n = makeNode(JsonSerializeExpr); + + n->expr = (JsonValueExpr *) $3; + n->output = (JsonOutput *) $4; + n->location = @1; + $$ = (Node *) n; + } + ; + /* * SQL/XML support @@ -16376,6 +16417,20 @@ json_encoding_clause_opt: | /* EMPTY */ { $$ = JS_ENC_DEFAULT; } ; +json_returning_clause_opt: + RETURNING Typename + { + JsonOutput *n = makeNode(JsonOutput); + + n->typeName = $2; + n->returning = makeNode(JsonReturning); + n->returning->format = + makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, @2); + $$ = (Node *) n; + } + | /* EMPTY */ { $$ = NULL; } + ; + json_output_clause_opt: RETURNING Typename json_format_clause_opt { @@ -17067,7 +17122,6 @@ unreserved_keyword: | INSTEAD | INVOKER | ISOLATION - | JSON | KEY | KEYS | LABEL @@ -17282,10 +17336,13 @@ col_name_keyword: | INT_P | INTEGER | INTERVAL + | JSON | JSON_ARRAY | JSON_ARRAYAGG | JSON_OBJECT | JSON_OBJECTAGG + | JSON_SCALAR + | JSON_SERIALIZE | LEAST | NATIONAL | NCHAR @@ -17646,6 +17703,8 @@ bare_label_keyword: | JSON_ARRAYAGG | JSON_OBJECT | JSON_OBJECTAGG + | JSON_SCALAR + | JSON_SERIALIZE | KEY | KEYS | LABEL diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index f02343ef64..b46e06237b 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -86,6 +86,10 @@ static Node *transformJsonArrayQueryConstructor(ParseState *pstate, static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg); static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg); static Node *transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred); +static Node *transformJsonParseExpr(ParseState *pstate, JsonParseExpr *expr); +static Node *transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *expr); +static Node *transformJsonSerializeExpr(ParseState *pstate, + JsonSerializeExpr * expr); static Node *make_row_comparison_op(ParseState *pstate, List *opname, List *largs, List *rargs, int location); static Node *make_row_distinct_op(ParseState *pstate, List *opname, @@ -337,6 +341,18 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr); break; + case T_JsonParseExpr: + result = transformJsonParseExpr(pstate, (JsonParseExpr *) expr); + break; + + case T_JsonScalarExpr: + result = transformJsonScalarExpr(pstate, (JsonScalarExpr *) expr); + break; + + case T_JsonSerializeExpr: + result = transformJsonSerializeExpr(pstate, (JsonSerializeExpr *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -3224,7 +3240,8 @@ makeCaseTestExpr(Node *expr) static Node * transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, char *constructName, - JsonFormatType default_format) + JsonFormatType default_format, + Oid targettype) { Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr); Node *rawexpr; @@ -3266,12 +3283,14 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, else format = default_format; - if (format != JS_FORMAT_DEFAULT) + if (format != JS_FORMAT_DEFAULT || + (OidIsValid(targettype) && exprtype != targettype)) { - Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID; Node *coerced; + bool cast_is_needed = OidIsValid(targettype); - if (exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING) + if (!cast_is_needed && + exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING) ereport(ERROR, errcode(ERRCODE_DATATYPE_MISMATCH), errmsg(ve->format->format_type == JS_FORMAT_DEFAULT ? @@ -3287,6 +3306,9 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, exprtype = TEXTOID; } + if (!OidIsValid(targettype)) + targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID; + /* Try to coerce to the target type. */ coerced = coerce_to_target_type(pstate, expr, exprtype, targettype, -1, @@ -3297,11 +3319,20 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, if (!coerced) { /* If coercion failed, use to_json()/to_jsonb() functions. */ - Oid fnoid = targettype == JSONOID ? F_TO_JSON : F_TO_JSONB; - FuncExpr *fexpr = makeFuncExpr(fnoid, targettype, - list_make1(expr), - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); + FuncExpr *fexpr; + Oid fnoid; + + if (cast_is_needed) /* only CAST is allowed */ + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(exprtype), + format_type_be(targettype)), + parser_errposition(pstate, location))); + + fnoid = targettype == JSONOID ? F_TO_JSON : F_TO_JSONB; + fexpr = makeFuncExpr(fnoid, targettype, list_make1(expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); fexpr->location = location; @@ -3584,7 +3615,8 @@ transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor) Node *key = transformExprRecurse(pstate, (Node *) kv->key); Node *val = transformJsonValueExpr(pstate, kv->value, "JSON_OBJECT()", - JS_FORMAT_DEFAULT); + JS_FORMAT_DEFAULT, + InvalidOid); args = lappend(args, key); args = lappend(args, val); @@ -3764,7 +3796,7 @@ transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg) key = transformExprRecurse(pstate, (Node *) agg->arg->key); val = transformJsonValueExpr(pstate, agg->arg->value, "JSON_OBJECTAGG()", - JS_FORMAT_DEFAULT); + JS_FORMAT_DEFAULT, InvalidOid); args = list_make2(key, val); returning = transformJsonConstructorOutput(pstate, agg->constructor->output, @@ -3821,7 +3853,7 @@ transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg) Oid aggtype; arg = transformJsonValueExpr(pstate, agg->arg, "JSON_ARRAYAGG()", - JS_FORMAT_DEFAULT); + JS_FORMAT_DEFAULT, InvalidOid); returning = transformJsonConstructorOutput(pstate, agg->constructor->output, list_make1(arg)); @@ -3869,7 +3901,8 @@ transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor) JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc)); Node *val = transformJsonValueExpr(pstate, jsval, "JSON_ARRAY()", - JS_FORMAT_DEFAULT); + JS_FORMAT_DEFAULT, + InvalidOid); args = lappend(args, val); } @@ -3952,3 +3985,145 @@ transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred) return makeJsonIsPredicate(expr, NULL, pred->item_type, pred->unique_keys, pred->location); } + +/* + * Transform the output clause of a JSON_*() expression if there is one and + * create one if not. + */ +static JsonReturning * +transformJsonReturning(ParseState *pstate, JsonOutput *output, const char *fname) +{ + JsonReturning *returning; + + if (output) + { + returning = transformJsonOutput(pstate, output, false); + + Assert(OidIsValid(returning->typid)); + + if (returning->typid != JSONOID && returning->typid != JSONBOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot use RETURNING type %s in %s", + format_type_be(returning->typid), fname), + parser_errposition(pstate, output->typeName->location))); + } + else + { + /* Output type is JSON by default. */ + Oid targettype = JSONOID; + JsonFormatType format = JS_FORMAT_JSON; + + returning = makeNode(JsonReturning); + returning->format = makeJsonFormat(format, JS_ENC_DEFAULT, -1); + returning->typid = targettype; + returning->typmod = -1; + } + + return returning; +} + +/* + * Transform a JSON() expression. + */ +static Node * +transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr) +{ + JsonReturning *returning = transformJsonReturning(pstate, jsexpr->output, + "JSON()"); + Node *arg; + + if (jsexpr->unique_keys) + { + /* + * Coerce string argument to text and then to json[b] in the executor + * node with key uniqueness check. + */ + JsonValueExpr *jve = jsexpr->expr; + Oid arg_type; + + arg = transformJsonParseArg(pstate, (Node *) jve->raw_expr, jve->format, + &arg_type); + + if (arg_type != TEXTOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot use non-string types with WITH UNIQUE KEYS clause"), + parser_errposition(pstate, jsexpr->location))); + } + else + { + /* + * Coerce argument to target type using CAST for compatibility with PG + * function-like CASTs. + */ + arg = transformJsonValueExpr(pstate, jsexpr->expr, "JSON()", + JS_FORMAT_JSON, returning->typid); + } + + return makeJsonConstructorExpr(pstate, JSCTOR_JSON_PARSE, list_make1(arg), NULL, + returning, jsexpr->unique_keys, false, + jsexpr->location); +} + +/* + * Transform a JSON_SCALAR() expression. + */ +static Node * +transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *jsexpr) +{ + Node *arg = transformExprRecurse(pstate, (Node *) jsexpr->expr); + JsonReturning *returning = transformJsonReturning(pstate, jsexpr->output, + "JSON_SCALAR()"); + + if (exprType(arg) == UNKNOWNOID) + arg = coerce_to_specific_type(pstate, arg, TEXTOID, "JSON_SCALAR"); + + return makeJsonConstructorExpr(pstate, JSCTOR_JSON_SCALAR, list_make1(arg), NULL, + returning, false, false, jsexpr->location); +} + +/* + * Transform a JSON_SERIALIZE() expression. + */ +static Node * +transformJsonSerializeExpr(ParseState *pstate, JsonSerializeExpr * expr) +{ + Node *arg = transformJsonValueExpr(pstate, expr->expr, + "JSON_SERIALIZE()", + JS_FORMAT_JSON, + InvalidOid); + JsonReturning *returning; + + if (expr->output) + { + returning = transformJsonOutput(pstate, expr->output, true); + + if (returning->typid != BYTEAOID) + { + char typcategory; + bool typispreferred; + + get_type_category_preferred(returning->typid, &typcategory, + &typispreferred); + if (typcategory != TYPCATEGORY_STRING) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot use RETURNING type %s in %s", + format_type_be(returning->typid), + "JSON_SERIALIZE()"), + errhint("Try returning a string type or bytea."))); + } + } + else + { + /* RETURNING TEXT FORMAT JSON is by default */ + returning = makeNode(JsonReturning); + returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1); + returning->typid = TEXTOID; + returning->typmod = -1; + } + + return makeJsonConstructorExpr(pstate, JSCTOR_JSON_SERIALIZE, list_make1(arg), + NULL, returning, false, false, expr->location); +} diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 4cca97ff9c..520d4f2a23 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1953,6 +1953,15 @@ FigureColnameInternal(Node *node, char **name) /* make XMLSERIALIZE act like a regular function */ *name = "xmlserialize"; return 2; + case T_JsonParseExpr: + *name = "json"; + return 2; + case T_JsonScalarExpr: + *name = "json_scalar"; + return 2; + case T_JsonSerializeExpr: + *name = "json_serialize"; + return 2; case T_JsonObjectConstructor: /* make JSON_OBJECT act like a regular function */ *name = "json_object"; diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c index 12402a0637..36c45a3978 100644 --- a/src/backend/utils/adt/format_type.c +++ b/src/backend/utils/adt/format_type.c @@ -294,6 +294,10 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags) else buf = pstrdup("character varying"); break; + + case JSONOID: + buf = pstrdup("json"); + break; } if (buf == NULL) diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 49080e5fbf..6d8fcdf40d 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -29,21 +29,6 @@ #include "utils/lsyscache.h" #include "utils/typcache.h" -typedef enum /* type categories for datum_to_json */ -{ - JSONTYPE_NULL, /* null, so we didn't bother to identify */ - JSONTYPE_BOOL, /* boolean (built-in types only) */ - JSONTYPE_NUMERIC, /* numeric (ditto) */ - JSONTYPE_DATE, /* we use special formatting for datetimes */ - JSONTYPE_TIMESTAMP, - JSONTYPE_TIMESTAMPTZ, - JSONTYPE_JSON, /* JSON itself (and JSONB) */ - JSONTYPE_ARRAY, /* array */ - JSONTYPE_COMPOSITE, /* composite */ - JSONTYPE_CAST, /* something with an explicit cast to JSON */ - JSONTYPE_OTHER /* all else */ -} JsonTypeCategory; - /* * Support for fast key uniqueness checking. @@ -107,9 +92,6 @@ static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, bool use_line_feeds); static void array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds); -static void json_categorize_type(Oid typoid, - JsonTypeCategory *tcategory, - Oid *outfuncoid); static void datum_to_json(Datum val, bool is_null, StringInfo result, JsonTypeCategory tcategory, Oid outfuncoid, bool key_scalar); @@ -188,8 +170,11 @@ json_recv(PG_FUNCTION_ARGS) * Given the datatype OID, return its JsonTypeCategory, as well as the type's * output function OID. If the returned category is JSONTYPE_CAST, we * return the OID of the type->JSON cast function instead. + * + * Note: exported for use in SQL/JSON executor functions, which cache the + * value of tcategory and outfuncoid. */ -static void +void json_categorize_type(Oid typoid, JsonTypeCategory *tcategory, Oid *outfuncoid) @@ -771,6 +756,20 @@ row_to_json_pretty(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } +/* + * Note: exported for use in SQL/JSON executor functions, which cache the + * value of tcategory and outfuncoid. + */ +Datum +to_json_worker(Datum val, JsonTypeCategory tcategory, Oid outfuncoid) +{ + StringInfo result = makeStringInfo(); + + datum_to_json(val, false, result, tcategory, outfuncoid, false); + + return PointerGetDatum(cstring_to_text_with_len(result->data, result->len)); +} + /* * Is the given type immutable when coming out of a JSON context? * @@ -821,7 +820,6 @@ to_json(PG_FUNCTION_ARGS) { Datum val = PG_GETARG_DATUM(0); Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0); - StringInfo result; JsonTypeCategory tcategory; Oid outfuncoid; @@ -833,11 +831,7 @@ to_json(PG_FUNCTION_ARGS) json_categorize_type(val_type, &tcategory, &outfuncoid); - result = makeStringInfo(); - - datum_to_json(val, false, result, tcategory, outfuncoid, false); - - PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); + PG_RETURN_DATUM(to_json_worker(val, tcategory, outfuncoid)); } /* diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index cf43c3f2de..7a91177a55 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -34,26 +34,10 @@ typedef struct JsonbInState { JsonbParseState *parseState; JsonbValue *res; + bool unique_keys; Node *escontext; } JsonbInState; -/* unlike with json categories, we need to treat json and jsonb differently */ -typedef enum /* type categories for datum_to_jsonb */ -{ - JSONBTYPE_NULL, /* null, so we didn't bother to identify */ - JSONBTYPE_BOOL, /* boolean (built-in types only) */ - JSONBTYPE_NUMERIC, /* numeric (ditto) */ - JSONBTYPE_DATE, /* we use special formatting for datetimes */ - JSONBTYPE_TIMESTAMP, /* we use special formatting for timestamp */ - JSONBTYPE_TIMESTAMPTZ, /* ... and timestamptz */ - JSONBTYPE_JSON, /* JSON */ - JSONBTYPE_JSONB, /* JSONB */ - JSONBTYPE_ARRAY, /* array */ - JSONBTYPE_COMPOSITE, /* composite */ - JSONBTYPE_JSONCAST, /* something with an explicit cast to JSON */ - JSONBTYPE_OTHER /* all else */ -} JsonbTypeCategory; - typedef struct JsonbAggState { JsonbInState *res; @@ -63,7 +47,8 @@ typedef struct JsonbAggState Oid val_output_func; } JsonbAggState; -static inline Datum jsonb_from_cstring(char *json, int len, Node *escontext); +static inline Datum jsonb_from_cstring(char *json, int len, bool unique_keys, + Node *escontext); static bool checkStringLen(size_t len, Node *escontext); static JsonParseErrorType jsonb_in_object_start(void *pstate); static JsonParseErrorType jsonb_in_object_end(void *pstate); @@ -72,17 +57,11 @@ static JsonParseErrorType jsonb_in_array_end(void *pstate); static JsonParseErrorType jsonb_in_object_field_start(void *pstate, char *fname, bool isnull); static void jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal); static JsonParseErrorType jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype); -static void jsonb_categorize_type(Oid typoid, - JsonbTypeCategory *tcategory, - Oid *outfuncoid); static void composite_to_jsonb(Datum composite, JsonbInState *result); static void array_dim_to_jsonb(JsonbInState *result, int dim, int ndims, int *dims, Datum *vals, bool *nulls, int *valcount, JsonbTypeCategory tcategory, Oid outfuncoid); static void array_to_jsonb_internal(Datum array, JsonbInState *result); -static void jsonb_categorize_type(Oid typoid, - JsonbTypeCategory *tcategory, - Oid *outfuncoid); static void datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, JsonbTypeCategory tcategory, Oid outfuncoid, bool key_scalar); @@ -100,7 +79,7 @@ jsonb_in(PG_FUNCTION_ARGS) { char *json = PG_GETARG_CSTRING(0); - return jsonb_from_cstring(json, strlen(json), fcinfo->context); + return jsonb_from_cstring(json, strlen(json), false, fcinfo->context); } /* @@ -124,7 +103,7 @@ jsonb_recv(PG_FUNCTION_ARGS) else elog(ERROR, "unsupported jsonb version number %d", version); - return jsonb_from_cstring(str, nbytes, NULL); + return jsonb_from_cstring(str, nbytes, false, NULL); } /* @@ -165,6 +144,18 @@ jsonb_send(PG_FUNCTION_ARGS) PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); } +/* + * Note: exported for use in SQL/JSON executor functions. + */ +Datum +jsonb_from_text(text *js, bool unique_keys) +{ + return jsonb_from_cstring(VARDATA_ANY(js), + VARSIZE_ANY_EXHDR(js), + unique_keys, + NULL); +} + /* * Get the type name of a jsonb container. */ @@ -258,7 +249,7 @@ jsonb_typeof(PG_FUNCTION_ARGS) * instead of being thrown. */ static inline Datum -jsonb_from_cstring(char *json, int len, Node *escontext) +jsonb_from_cstring(char *json, int len, bool unique_keys, Node *escontext) { JsonLexContext *lex; JsonbInState state; @@ -268,6 +259,7 @@ jsonb_from_cstring(char *json, int len, Node *escontext) memset(&sem, 0, sizeof(sem)); lex = makeJsonLexContextCstringLen(json, len, GetDatabaseEncoding(), true); + state.unique_keys = unique_keys; state.escontext = escontext; sem.semstate = (void *) &state; @@ -304,6 +296,7 @@ jsonb_in_object_start(void *pstate) JsonbInState *_state = (JsonbInState *) pstate; _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_OBJECT, NULL); + _state->parseState->unique_keys = _state->unique_keys; return JSON_SUCCESS; } @@ -639,8 +632,11 @@ add_indent(StringInfo out, bool indent, int level) * Given the datatype OID, return its JsonbTypeCategory, as well as the type's * output function OID. If the returned category is JSONBTYPE_JSONCAST, * we return the OID of the relevant cast function instead. + * + * Note: exported for use in SQL/JSON executor functions, which cache the + * value of tcategory and outfuncoid. */ -static void +void jsonb_categorize_type(Oid typoid, JsonbTypeCategory *tcategory, Oid *outfuncoid) @@ -1150,6 +1146,23 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result, datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar); } + +/* + * Note: exported for use in SQL/JSON executor functions, which cache the + * value of tcategory and outfuncoid. + */ +Datum +to_jsonb_worker(Datum val, JsonbTypeCategory tcategory, Oid outfuncoid) +{ + JsonbInState result; + + memset(&result, 0, sizeof(JsonbInState)); + + datum_to_jsonb(val, false, &result, tcategory, outfuncoid, false); + + return JsonbPGetDatum(JsonbValueToJsonb(result.res)); +} + /* * Is the given type immutable when coming out of a JSONB context? * @@ -1201,7 +1214,6 @@ to_jsonb(PG_FUNCTION_ARGS) { Datum val = PG_GETARG_DATUM(0); Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0); - JsonbInState result; JsonbTypeCategory tcategory; Oid outfuncoid; @@ -1213,11 +1225,7 @@ to_jsonb(PG_FUNCTION_ARGS) jsonb_categorize_type(val_type, &tcategory, &outfuncoid); - memset(&result, 0, sizeof(JsonbInState)); - - datum_to_jsonb(val, false, &result, tcategory, outfuncoid, false); - - PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); + PG_RETURN_DATUM(to_jsonb_worker(val, tcategory, outfuncoid)); } Datum diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index d3a973d86b..d1b03d6cb2 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -10832,6 +10832,15 @@ get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context, case JSCTOR_JSON_ARRAY: funcname = "JSON_ARRAY"; break; + case JSCTOR_JSON_PARSE: + funcname = "JSON"; + break; + case JSCTOR_JSON_SCALAR: + funcname = "JSON_SCALAR"; + break; + case JSCTOR_JSON_SERIALIZE: + funcname = "JSON_SERIALIZE"; + break; default: elog(ERROR, "invalid JsonConstructorType %d", ctor->type); } @@ -10879,7 +10888,10 @@ get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf) if (ctor->unique) appendStringInfoString(buf, " WITH UNIQUE KEYS"); - get_json_returning(ctor->returning, buf, true); + if (!((ctor->type == JSCTOR_JSON_PARSE || + ctor->type == JSCTOR_JSON_SCALAR) && + ctor->returning->typid == JSONOID)) + get_json_returning(ctor->returning, buf, true); } /* diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 88b03cc472..06a124a86c 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1717,6 +1717,17 @@ typedef struct TriggerTransition /* Nodes for SQL/JSON support */ +/* + * JsonQuotes - + * representation of [KEEP|OMIT] QUOTES clause for JSON_QUERY() + */ +typedef enum JsonQuotes +{ + JS_QUOTES_UNSPEC, /* unspecified */ + JS_QUOTES_KEEP, /* KEEP QUOTES */ + JS_QUOTES_OMIT /* OMIT QUOTES */ +} JsonQuotes; + /* * JsonOutput - * representation of JSON output clause (RETURNING type [FORMAT format]) @@ -1740,6 +1751,43 @@ typedef struct JsonKeyValue JsonValueExpr *value; /* JSON value expression */ } JsonKeyValue; +/* + * JsonParseExpr - + * untransformed representation of JSON() + */ +typedef struct JsonParseExpr +{ + NodeTag type; + JsonValueExpr *expr; /* string expression */ + JsonOutput *output; /* RETURNING clause, if specified */ + bool unique_keys; /* WITH UNIQUE KEYS? */ + int location; /* token location, or -1 if unknown */ +} JsonParseExpr; + +/* + * JsonScalarExpr - + * untransformed representation of JSON_SCALAR() + */ +typedef struct JsonScalarExpr +{ + NodeTag type; + Expr *expr; /* scalar expression */ + JsonOutput *output; /* RETURNING clause, if specified */ + int location; /* token location, or -1 if unknown */ +} JsonScalarExpr; + +/* + * JsonSerializeExpr - + * untransformed representation of JSON_SERIALIZE() function + */ +typedef struct JsonSerializeExpr +{ + NodeTag type; + JsonValueExpr *expr; /* json value expression */ + JsonOutput *output; /* RETURNING clause, if specified */ + int location; /* token location, or -1 if unknown */ +} JsonSerializeExpr; + /* * JsonObjectConstructor - * untransformed representation of JSON_OBJECT() constructor diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 792a743f72..6c6a1810af 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1607,7 +1607,10 @@ typedef enum JsonConstructorType JSCTOR_JSON_OBJECT = 1, JSCTOR_JSON_ARRAY = 2, JSCTOR_JSON_OBJECTAGG = 3, - JSCTOR_JSON_ARRAYAGG = 4 + JSCTOR_JSON_ARRAYAGG = 4, + JSCTOR_JSON_SCALAR = 5, + JSCTOR_JSON_SERIALIZE = 6, + JSCTOR_JSON_PARSE = 7 } JsonConstructorType; /* diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index f5b2e61ca5..5984dcfa4b 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -230,11 +230,13 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL) PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) -PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("json", JSON, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_scalar", JSON_SCALAR, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_serialize", JSON_SERIALIZE, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL) diff --git a/src/include/utils/json.h b/src/include/utils/json.h index 35a9a5545d..4c0e0bd09d 100644 --- a/src/include/utils/json.h +++ b/src/include/utils/json.h @@ -16,11 +16,30 @@ #include "lib/stringinfo.h" +typedef enum /* type categories for datum_to_json */ +{ + JSONTYPE_NULL, /* null, so we didn't bother to identify */ + JSONTYPE_BOOL, /* boolean (built-in types only) */ + JSONTYPE_NUMERIC, /* numeric (ditto) */ + JSONTYPE_DATE, /* we use special formatting for datetimes */ + JSONTYPE_TIMESTAMP, + JSONTYPE_TIMESTAMPTZ, + JSONTYPE_JSON, /* JSON itself (and JSONB) */ + JSONTYPE_ARRAY, /* array */ + JSONTYPE_COMPOSITE, /* composite */ + JSONTYPE_CAST, /* something with an explicit cast to JSON */ + JSONTYPE_OTHER /* all else */ +} JsonTypeCategory; + /* functions in json.c */ extern void escape_json(StringInfo buf, const char *str); extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp); extern bool to_json_is_immutable(Oid typoid); +extern void json_categorize_type(Oid typoid, JsonTypeCategory *tcategory, + Oid *outfuncoid); +extern Datum to_json_worker(Datum val, JsonTypeCategory tcategory, + Oid outfuncoid); extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types, bool absent_on_null, bool unique_keys); diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 649a1644f2..8417a74298 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -368,6 +368,22 @@ typedef struct JsonbIterator struct JsonbIterator *parent; } JsonbIterator; +/* unlike with json categories, we need to treat json and jsonb differently */ +typedef enum /* type categories for datum_to_jsonb */ +{ + JSONBTYPE_NULL, /* null, so we didn't bother to identify */ + JSONBTYPE_BOOL, /* boolean (built-in types only) */ + JSONBTYPE_NUMERIC, /* numeric (ditto) */ + JSONBTYPE_DATE, /* we use special formatting for datetimes */ + JSONBTYPE_TIMESTAMP, /* we use special formatting for timestamp */ + JSONBTYPE_TIMESTAMPTZ, /* ... and timestamptz */ + JSONBTYPE_JSON, /* JSON */ + JSONBTYPE_JSONB, /* JSONB */ + JSONBTYPE_ARRAY, /* array */ + JSONBTYPE_COMPOSITE, /* composite */ + JSONBTYPE_JSONCAST, /* something with an explicit cast to JSON */ + JSONBTYPE_OTHER /* all else */ +} JsonbTypeCategory; /* Convenience macros */ static inline Jsonb * @@ -418,6 +434,7 @@ extern void JsonbHashScalarValueExtended(const JsonbValue *scalarVal, uint64 *hash, uint64 seed); /* jsonb.c support functions */ +extern Datum jsonb_from_text(text *js, bool unique_keys); extern char *JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len); extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in, @@ -430,6 +447,10 @@ extern Datum jsonb_set_element(Jsonb *jb, Datum *path, int path_len, extern Datum jsonb_get_element(Jsonb *jb, Datum *path, int npath, bool *isnull, bool as_text); extern bool to_jsonb_is_immutable(Oid typoid); +extern void jsonb_categorize_type(Oid typoid, JsonbTypeCategory *tcategory, + Oid *outfuncoid); +extern Datum to_jsonb_worker(Datum val, JsonbTypeCategory tcategory, + Oid outfuncoid); extern Datum jsonb_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types, bool absent_on_null, bool unique_keys); diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index d73c7e2c6c..f0f5ae8119 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -1,3 +1,337 @@ +-- JSON() +SELECT JSON(); +ERROR: syntax error at or near ")" +LINE 1: SELECT JSON(); + ^ +SELECT JSON(NULL); + json +------ + +(1 row) + +SELECT JSON('{ "a" : 1 } '); + json +-------------- + { "a" : 1 } +(1 row) + +SELECT JSON('{ "a" : 1 } ' FORMAT JSON); + json +-------------- + { "a" : 1 } +(1 row) + +SELECT JSON('{ "a" : 1 } ' FORMAT JSON ENCODING UTF8); +ERROR: JSON ENCODING clause is only allowed for bytea input type +LINE 1: SELECT JSON('{ "a" : 1 } ' FORMAT JSON ENCODING UTF8); + ^ +SELECT JSON('{ "a" : 1 } '::bytea FORMAT JSON ENCODING UTF8); + json +-------------- + { "a" : 1 } +(1 row) + +SELECT pg_typeof(JSON('{ "a" : 1 } ')); + pg_typeof +----------- + json +(1 row) + +SELECT JSON(' 1 '::json); + json +--------- + 1 +(1 row) + +SELECT JSON(' 1 '::jsonb); + json +------ + 1 +(1 row) + +SELECT JSON(' 1 '::json WITH UNIQUE KEYS); +ERROR: cannot use non-string types with WITH UNIQUE KEYS clause +LINE 1: SELECT JSON(' 1 '::json WITH UNIQUE KEYS); + ^ +SELECT JSON(123); +ERROR: cannot cast type integer to json +LINE 1: SELECT JSON(123); + ^ +SELECT JSON('{"a": 1, "a": 2}'); + json +------------------ + {"a": 1, "a": 2} +(1 row) + +SELECT JSON('{"a": 1, "a": 2}' WITH UNIQUE KEYS); +ERROR: duplicate JSON object key value +SELECT JSON('{"a": 1, "a": 2}' WITHOUT UNIQUE KEYS); + json +------------------ + {"a": 1, "a": 2} +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'); + QUERY PLAN +----------------------------- + Result + Output: JSON('123'::json) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' FORMAT JSON); + QUERY PLAN +----------------------------- + Result + Output: JSON('123'::json) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON); + QUERY PLAN +----------------------------------------------- + Result + Output: JSON('\x313233'::bytea FORMAT JSON) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON ENCODING UTF8); + QUERY PLAN +------------------------------------------------------------- + Result + Output: JSON('\x313233'::bytea FORMAT JSON ENCODING UTF8) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITH UNIQUE KEYS); + QUERY PLAN +---------------------------------------------- + Result + Output: JSON('123'::text WITH UNIQUE KEYS) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITHOUT UNIQUE KEYS); + QUERY PLAN +----------------------------- + Result + Output: JSON('123'::json) +(2 rows) + +SELECT JSON('123' RETURNING text); +ERROR: cannot use RETURNING type text in JSON() +LINE 1: SELECT JSON('123' RETURNING text); + ^ +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'); + QUERY PLAN +----------------------------- + Result + Output: JSON('123'::json) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json); + QUERY PLAN +----------------------------- + Result + Output: JSON('123'::json) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb); + QUERY PLAN +---------------------------------------------- + Result + Output: JSON('123'::jsonb RETURNING jsonb) +(2 rows) + +SELECT pg_typeof(JSON('123')); + pg_typeof +----------- + json +(1 row) + +SELECT pg_typeof(JSON('123' RETURNING json)); + pg_typeof +----------- + json +(1 row) + +SELECT pg_typeof(JSON('123' RETURNING jsonb)); + pg_typeof +----------- + jsonb +(1 row) + +-- JSON_SCALAR() +SELECT JSON_SCALAR(); +ERROR: syntax error at or near ")" +LINE 1: SELECT JSON_SCALAR(); + ^ +SELECT JSON_SCALAR(NULL); + json_scalar +------------- + +(1 row) + +SELECT JSON_SCALAR(NULL::int); + json_scalar +------------- + +(1 row) + +SELECT JSON_SCALAR(123); + json_scalar +------------- + 123 +(1 row) + +SELECT JSON_SCALAR(123.45); + json_scalar +------------- + 123.45 +(1 row) + +SELECT JSON_SCALAR(123.45::numeric); + json_scalar +------------- + 123.45 +(1 row) + +SELECT JSON_SCALAR(true); + json_scalar +------------- + true +(1 row) + +SELECT JSON_SCALAR(false); + json_scalar +------------- + false +(1 row) + +SELECT JSON_SCALAR(' 123.45'); + json_scalar +------------- + " 123.45" +(1 row) + +SELECT JSON_SCALAR('2020-06-07'::date); + json_scalar +-------------- + "2020-06-07" +(1 row) + +SELECT JSON_SCALAR('2020-06-07 01:02:03'::timestamp); + json_scalar +----------------------- + "2020-06-07T01:02:03" +(1 row) + +SELECT JSON_SCALAR('{}'::json); + json_scalar +------------- + {} +(1 row) + +SELECT JSON_SCALAR('{}'::jsonb); + json_scalar +------------- + {} +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123); + QUERY PLAN +---------------------------- + Result + Output: JSON_SCALAR(123) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123'); + QUERY PLAN +------------------------------------ + Result + Output: JSON_SCALAR('123'::text) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json); + QUERY PLAN +---------------------------- + Result + Output: JSON_SCALAR(123) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb); + QUERY PLAN +-------------------------------------------- + Result + Output: JSON_SCALAR(123 RETURNING jsonb) +(2 rows) + +-- JSON_SERIALIZE() +SELECT JSON_SERIALIZE(); +ERROR: syntax error at or near ")" +LINE 1: SELECT JSON_SERIALIZE(); + ^ +SELECT JSON_SERIALIZE(NULL); + json_serialize +---------------- + +(1 row) + +SELECT JSON_SERIALIZE(JSON('{ "a" : 1 } ')); + json_serialize +---------------- + { "a" : 1 } +(1 row) + +SELECT JSON_SERIALIZE('{ "a" : 1 } '); + json_serialize +---------------- + { "a" : 1 } +(1 row) + +SELECT JSON_SERIALIZE('1'); + json_serialize +---------------- + 1 +(1 row) + +SELECT JSON_SERIALIZE('1' FORMAT JSON); + json_serialize +---------------- + 1 +(1 row) + +SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING bytea); + json_serialize +---------------------------- + \x7b20226122203a2031207d20 +(1 row) + +SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING varchar); + json_serialize +---------------- + { "a" : 1 } +(1 row) + +SELECT pg_typeof(JSON_SERIALIZE(NULL)); + pg_typeof +----------- + text +(1 row) + +-- only string types or bytea allowed +SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING jsonb); +ERROR: cannot use RETURNING type jsonb in JSON_SERIALIZE() +HINT: Try returning a string type or bytea. +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SERIALIZE('{}'); + QUERY PLAN +----------------------------------------------------- + Result + Output: JSON_SERIALIZE('{}'::json RETURNING text) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SERIALIZE('{}' RETURNING bytea); + QUERY PLAN +------------------------------------------------------ + Result + Output: JSON_SERIALIZE('{}'::json RETURNING bytea) +(2 rows) + -- JSON_OBJECT() SELECT JSON_OBJECT(); json_object @@ -630,6 +964,13 @@ ERROR: duplicate JSON object key SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb) FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); ERROR: duplicate JSON object key +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb) +FROM (VALUES (1, 1), (0, NULL),(4, null), (5, null),(6, null),(2, 2)) foo(k, v); + json_objectagg +------------------ + {"1": 1, "2": 2} +(1 row) + -- Test JSON_OBJECT deparsing EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json); @@ -645,6 +986,41 @@ SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json); CREATE OR REPLACE VIEW public.json_object_view AS SELECT JSON_OBJECT('foo' : '1'::text FORMAT JSON, 'bar' : 'baz'::text RETURNING json) AS "json_object" DROP VIEW json_object_view; +SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v WITH UNIQUE KEYS) OVER (ORDER BY k) +FROM (VALUES (1,1), (2,2)) a(k,v); + a | json_objectagg +---------------+---------------------- + {"k":1,"v":1} | { "1" : 1 } + {"k":2,"v":2} | { "1" : 1, "2" : 2 } +(2 rows) + +SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v WITH UNIQUE KEYS) OVER (ORDER BY k) +FROM (VALUES (1,1), (1,2), (2,2)) a(k,v); +ERROR: duplicate JSON key "1" +SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v ABSENT ON NULL WITH UNIQUE KEYS) + OVER (ORDER BY k) +FROM (VALUES (1,1), (1,null), (2,2)) a(k,v); +ERROR: duplicate JSON key "1" +SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v ABSENT ON NULL) +OVER (ORDER BY k) +FROM (VALUES (1,1), (1,null), (2,2)) a(k,v); + a | json_objectagg +------------------+---------------------- + {"k":1,"v":1} | { "1" : 1 } + {"k":1,"v":null} | { "1" : 1 } + {"k":2,"v":2} | { "1" : 1, "2" : 2 } +(3 rows) + +SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v ABSENT ON NULL) +OVER (ORDER BY k RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) +FROM (VALUES (1,1), (1,null), (2,2)) a(k,v); + a | json_objectagg +------------------+---------------------- + {"k":1,"v":1} | { "1" : 1, "2" : 2 } + {"k":1,"v":null} | { "1" : 1, "2" : 2 } + {"k":2,"v":2} | { "1" : 1, "2" : 2 } +(3 rows) + -- Test JSON_ARRAY deparsing EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json); diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql index 4fd820fd51..e338ef706b 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -1,3 +1,75 @@ +-- JSON() +SELECT JSON(); +SELECT JSON(NULL); +SELECT JSON('{ "a" : 1 } '); +SELECT JSON('{ "a" : 1 } ' FORMAT JSON); +SELECT JSON('{ "a" : 1 } ' FORMAT JSON ENCODING UTF8); +SELECT JSON('{ "a" : 1 } '::bytea FORMAT JSON ENCODING UTF8); +SELECT pg_typeof(JSON('{ "a" : 1 } ')); + +SELECT JSON(' 1 '::json); +SELECT JSON(' 1 '::jsonb); +SELECT JSON(' 1 '::json WITH UNIQUE KEYS); +SELECT JSON(123); + +SELECT JSON('{"a": 1, "a": 2}'); +SELECT JSON('{"a": 1, "a": 2}' WITH UNIQUE KEYS); +SELECT JSON('{"a": 1, "a": 2}' WITHOUT UNIQUE KEYS); + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' FORMAT JSON); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON ENCODING UTF8); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITH UNIQUE KEYS); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITHOUT UNIQUE KEYS); + +SELECT JSON('123' RETURNING text); + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb); +SELECT pg_typeof(JSON('123')); +SELECT pg_typeof(JSON('123' RETURNING json)); +SELECT pg_typeof(JSON('123' RETURNING jsonb)); + +-- JSON_SCALAR() +SELECT JSON_SCALAR(); +SELECT JSON_SCALAR(NULL); +SELECT JSON_SCALAR(NULL::int); +SELECT JSON_SCALAR(123); +SELECT JSON_SCALAR(123.45); +SELECT JSON_SCALAR(123.45::numeric); +SELECT JSON_SCALAR(true); +SELECT JSON_SCALAR(false); +SELECT JSON_SCALAR(' 123.45'); +SELECT JSON_SCALAR('2020-06-07'::date); +SELECT JSON_SCALAR('2020-06-07 01:02:03'::timestamp); +SELECT JSON_SCALAR('{}'::json); +SELECT JSON_SCALAR('{}'::jsonb); + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123'); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb); + +-- JSON_SERIALIZE() +SELECT JSON_SERIALIZE(); +SELECT JSON_SERIALIZE(NULL); +SELECT JSON_SERIALIZE(JSON('{ "a" : 1 } ')); +SELECT JSON_SERIALIZE('{ "a" : 1 } '); +SELECT JSON_SERIALIZE('1'); +SELECT JSON_SERIALIZE('1' FORMAT JSON); +SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING bytea); +SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING varchar); +SELECT pg_typeof(JSON_SERIALIZE(NULL)); + +-- only string types or bytea allowed +SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING jsonb); + + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SERIALIZE('{}'); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SERIALIZE('{}' RETURNING bytea); + -- JSON_OBJECT() SELECT JSON_OBJECT(); SELECT JSON_OBJECT(RETURNING json); @@ -216,6 +288,9 @@ FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb) FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb) +FROM (VALUES (1, 1), (0, NULL),(4, null), (5, null),(6, null),(2, 2)) foo(k, v); + -- Test JSON_OBJECT deparsing EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json); @@ -227,6 +302,24 @@ SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json); DROP VIEW json_object_view; +SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v WITH UNIQUE KEYS) OVER (ORDER BY k) +FROM (VALUES (1,1), (2,2)) a(k,v); + +SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v WITH UNIQUE KEYS) OVER (ORDER BY k) +FROM (VALUES (1,1), (1,2), (2,2)) a(k,v); + +SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v ABSENT ON NULL WITH UNIQUE KEYS) + OVER (ORDER BY k) +FROM (VALUES (1,1), (1,null), (2,2)) a(k,v); + +SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v ABSENT ON NULL) +OVER (ORDER BY k) +FROM (VALUES (1,1), (1,null), (2,2)) a(k,v); + +SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v ABSENT ON NULL) +OVER (ORDER BY k RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) +FROM (VALUES (1,1), (1,null), (2,2)) a(k,v); + -- Test JSON_ARRAY deparsing EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index e941fb6c82..7d60511e9e 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1296,6 +1296,7 @@ JsonPathPredicateCallback JsonPathString JsonReturning JsonSemAction +JsonSerializeExpr JsonTokenType JsonTransformStringValuesAction JsonTypeCategory -- 2.35.3