RETURNING clause for JSON() and JSON_SCALAR()
authorAndrew Dunstan <andrew@dunslane.net>
Sat, 5 Mar 2022 13:07:15 +0000 (08:07 -0500)
committerAndrew Dunstan <andrew@dunslane.net>
Thu, 31 Mar 2022 19:45:24 +0000 (15:45 -0400)
This patch is extracted from a larger patch that allowed setting the
default returned value from these functions to json or jsonb. That had
problems, but this piece of it is fine. For these functions only json or
jsonb can be specified in the RETURNING clause.

Extracted from an original patch from 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

src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/nodeFuncs.c
src/backend/parser/gram.y
src/backend/parser/parse_expr.c
src/backend/utils/adt/ruleutils.c
src/include/nodes/parsenodes.h
src/test/regress/expected/sqljson.out
src/test/regress/sql/sqljson.sql

index 56505557bff9c5c7f0ad2d38f53377eb00f9b42f..11c016495e3e94b87b61795b5dcf3b1791872e99 100644 (file)
@@ -2354,6 +2354,7 @@ _copyJsonParseExpr(const JsonParseExpr *from)
    JsonParseExpr  *newnode = makeNode(JsonParseExpr);
 
    COPY_NODE_FIELD(expr);
+   COPY_NODE_FIELD(output);
    COPY_SCALAR_FIELD(unique_keys);
    COPY_LOCATION_FIELD(location);
 
@@ -2369,6 +2370,7 @@ _copyJsonScalarExpr(const JsonScalarExpr *from)
    JsonScalarExpr *newnode = makeNode(JsonScalarExpr);
 
    COPY_NODE_FIELD(expr);
+   COPY_NODE_FIELD(output);
    COPY_LOCATION_FIELD(location);
 
    return newnode;
index 9ea3c5abf2391c89b69f7e64ba74e87c073e2e6f..722dbe6a0d853fe82f603415aef9a0ac384827d2 100644 (file)
@@ -875,6 +875,7 @@ static bool
 _equalJsonParseExpr(const JsonParseExpr *a, const JsonParseExpr *b)
 {
    COMPARE_NODE_FIELD(expr);
+   COMPARE_NODE_FIELD(output);
    COMPARE_SCALAR_FIELD(unique_keys);
    COMPARE_LOCATION_FIELD(location);
 
@@ -885,6 +886,7 @@ static bool
 _equalJsonScalarExpr(const JsonScalarExpr *a, const JsonScalarExpr *b)
 {
    COMPARE_NODE_FIELD(expr);
+   COMPARE_NODE_FIELD(output);
    COMPARE_LOCATION_FIELD(location);
 
    return true;
index 4789ba6911300b63f4a07b32604cc4544c252cbb..a094317bfc1c1d1b9ef235402fcd3690c5109e99 100644 (file)
@@ -4364,9 +4364,25 @@ raw_expression_tree_walker(Node *node,
            }
            break;
        case T_JsonParseExpr:
-           return walker(((JsonParseExpr *) node)->expr, context);
+           {
+               JsonParseExpr *jpe = (JsonParseExpr *) node;
+
+               if (walker(jpe->expr, context))
+                   return true;
+               if (walker(jpe->output, context))
+                   return true;
+           }
+           break;
        case T_JsonScalarExpr:
-           return walker(((JsonScalarExpr *) node)->expr, context);
+           {
+               JsonScalarExpr *jse = (JsonScalarExpr *) node;
+
+               if (walker(jse->expr, context))
+                   return true;
+               if (walker(jse->output, context))
+                   return true;
+           }
+           break;
        case T_JsonSerializeExpr:
            {
                JsonSerializeExpr *jse = (JsonSerializeExpr *) node;
index eefcf901879a517c5ca3ad4002af568a4f7929f1..e5a3c528aad6ba42cc2e8430340452f618cff140 100644 (file)
@@ -15614,21 +15614,24 @@ json_func_expr:
        ;
 
 json_parse_expr:
-           JSON '(' json_value_expr json_key_uniqueness_constraint_opt ')'
+           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_expr:
-           JSON_SCALAR '(' a_expr ')'
+           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;
                }
index e14000725016515ca1a54900d8df8fa14827beb3..31f0c9f693d28c2b160fd24a062b8e0a72781e59 100644 (file)
@@ -4450,19 +4450,48 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
    return (Node *) jsexpr;
 }
 
+static JsonReturning *
+transformJsonConstructorRet(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
+   {
+       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 = makeNode(JsonReturning);
+   JsonReturning *returning = transformJsonConstructorRet(pstate, jsexpr->output,
+                                                   "JSON()");
    Node       *arg;
 
-   returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1);
-   returning->typid = JSONOID;
-   returning->typmod = -1;
-
    if (jsexpr->unique_keys)
    {
        /*
@@ -4502,12 +4531,9 @@ transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr)
 static Node *
 transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *jsexpr)
 {
-   JsonReturning *returning = makeNode(JsonReturning);
    Node       *arg = transformExprRecurse(pstate, (Node *) jsexpr->expr);
-
-   returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1);
-   returning->typid = JSONOID;
-   returning->typmod = -1;
+   JsonReturning *returning = transformJsonConstructorRet(pstate, jsexpr->output,
+                                                   "JSON_SCALAR()");
 
    if (exprType(arg) == UNKNOWNOID)
        arg = coerce_to_specific_type(pstate, arg, TEXTOID, "JSON_SCALAR");
index 010d5a7a751c16bffc0b8d5de342ef3df88b4005..4458d2ff90a533e546183246916120995e996817 100644 (file)
@@ -10092,8 +10092,9 @@ get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf)
    if (ctor->unique)
        appendStringInfoString(buf, " WITH UNIQUE KEYS");
 
-   if (ctor->type != JSCTOR_JSON_PARSE &&
-       ctor->type != JSCTOR_JSON_SCALAR)
+   if (!((ctor->type == JSCTOR_JSON_PARSE ||
+          ctor->type == JSCTOR_JSON_SCALAR) &&
+         ctor->returning->typid == JSONOID))
        get_json_returning(ctor->returning, buf, true);
 }
 
index c24fc26da1740fe0c4b2925050551ce1ed0b5d13..8a9ccf62210786b82dd0d1fda4a4a243f4bff25a 100644 (file)
@@ -1684,6 +1684,7 @@ 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;
@@ -1696,6 +1697,7 @@ typedef struct JsonScalarExpr
 {
    NodeTag     type;
    Expr       *expr;           /* scalar expression */
+   JsonOutput *output;         /* RETURNING clause, if specified */
    int         location;       /* token location, or -1 if unknown */
 } JsonScalarExpr;
 
index 11f5eb2d2cee3fa58054a1e592ec54b52c65d8c1..6cadd87868a779091937173579457a4306986226 100644 (file)
@@ -113,6 +113,49 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITHOUT UNIQUE KEYS);
    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 ")"
@@ -204,6 +247,20 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123');
    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 ")"
index 98bd93c1104206cb5db0214c76b6744763e74623..51fc659b58cd9590b02a357020476ef51660dcde 100644 (file)
@@ -23,6 +23,14 @@ 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();
@@ -41,6 +49,8 @@ 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();