Add more SQL/JSON constructor functions
authorAmit Langote <amitlan@postgresql.org>
Thu, 20 Jul 2023 13:21:43 +0000 (22:21 +0900)
committerAmit Langote <amitlan@postgresql.org>
Wed, 26 Jul 2023 08:08:33 +0000 (17:08 +0900)
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.

Catversion bumped as this changes ruleutils.c.

Author: Nikita Glukhov <n.gluhov@postgrespro.ru>
Author: Teodor Sigaev <teodor@sigaev.ru>
Author: Oleg Bartunov <obartunov@gmail.com>
Author: Alexander Korotkov <aekorotkov@gmail.com>
Author: Andrew Dunstan <andrew@dunslane.net>
Author: Amit Langote <amitlangote09@gmail.com>

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, Álvaro Herrera,
Peter Eisentraut

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
Discussion: https://postgr.es/m/CA+HiwqE4XTdfb1nW=Ojoy_tQSRhYt-q_kb6i5d4xcKyrLC1Nbg@mail.gmail.com

22 files changed:
doc/src/sgml/func.sgml
src/backend/executor/execExpr.c
src/backend/executor/execExprInterp.c
src/backend/nodes/nodeFuncs.c
src/backend/parser/gram.y
src/backend/parser/parse_expr.c
src/backend/parser/parse_target.c
src/backend/utils/adt/format_type.c
src/backend/utils/adt/jsonb.c
src/backend/utils/adt/ruleutils.c
src/include/catalog/catversion.h
src/include/nodes/parsenodes.h
src/include/nodes/primnodes.h
src/include/parser/kwlist.h
src/include/utils/jsonfuncs.h
src/interfaces/ecpg/test/expected/sql-sqljson.c
src/interfaces/ecpg/test/expected/sql-sqljson.stderr
src/interfaces/ecpg/test/expected/sql-sqljson.stdout
src/interfaces/ecpg/test/sql/sqljson.pgc
src/test/regress/expected/sqljson.out
src/test/regress/sql/sqljson.sql
src/tools/pgindent/typedefs.list

index b94827674c9499f2f44f5b38b47f653091b178a0..dcc9d6f59d7da53be09eb2ceb9bb6ea38324bf79 100644 (file)
@@ -16001,6 +16001,72 @@ table2-mapping
         <returnvalue>{"a": "1", "b": "2"}</returnvalue>
        </para></entry>
       </row>
+      <row>
+       <entry role="func_table_entry">
+        <para role="func_signature">
+         <indexterm><primary>json constructor</primary></indexterm>
+         <function>json</function> (
+         <replaceable>expression</replaceable>
+         <optional> <literal>FORMAT JSON</literal> <optional> <literal>ENCODING UTF8</literal> </optional></optional>
+         <optional> { <literal>WITH</literal> | <literal>WITHOUT</literal> } <literal>UNIQUE</literal> <optional> <literal>KEYS</literal> </optional></optional>
+        </para>
+        <para>
+         Converts a given expression specified as <type>text</type> or
+         <type>bytea</type> string (in UTF8 encoding) into a JSON
+         value.  If <replaceable>expression</replaceable> is NULL, an
+         <acronym>SQL</acronym> null value is returned.
+         If <literal>WITH UNIQUE</literal> is specified, the
+         <replaceable>expression</replaceable> must not contain any duplicate
+         object keys.
+        </para>
+        <para>
+         <literal>json('{"a":123, "b":[true,"foo"], "a":"bar"}')</literal>
+         <returnvalue>{"a":123, "b":[true,"foo"], "a":"bar"}</returnvalue>
+        </para>
+       </entry>
+      </row>
+      <row>
+       <entry role="func_table_entry">
+        <para role="func_signature">
+        <indexterm><primary>json_scalar</primary></indexterm>
+        <function>json_scalar</function> (<replaceable>expression</replaceable>)
+       </para>
+       <para>
+        Converts a given SQL scalar value into a JSON scalar value.
+        If the input is NULL, an <acronym>SQL</acronym> null is returned. If
+        the input is number or a boolean value, a corresponding JSON number
+        or boolean value is returned. For any other value, a JSON string is
+        returned.
+       </para>
+       <para>
+        <literal>json_scalar(123.45)</literal>
+        <returnvalue>123.45</returnvalue>
+       </para>
+       <para>
+        <literal>json_scalar(CURRENT_TIMESTAMP)</literal>
+        <returnvalue>"2022-05-10T10:51:04.62128-04:00"</returnvalue>
+      </para></entry>
+     </row>
+     <row>
+      <entry role="func_table_entry">
+       <para role="func_signature">
+        <function>json_serialize</function> (
+        <replaceable>expression</replaceable> <optional> <literal>FORMAT JSON</literal> <optional> <literal>ENCODING UTF8</literal> </optional> </optional>
+        <optional> <literal>RETURNING</literal> <replaceable>data_type</replaceable> <optional> <literal>FORMAT JSON</literal> <optional> <literal>ENCODING UTF8</literal> </optional> </optional> </optional>)
+       </para>
+       <para>
+        Converts an SQL/JSON expression into a character or binary string. The
+        <replaceable>expression</replaceable> can be of any JSON type, any
+        character string type, or <type>bytea</type> in UTF8 encoding.
+        The returned type used in <literal> RETURNING</literal> can be any
+        character string type or <type>bytea</type>. The default is
+        <type>text</type>.
+       </para>
+       <para>
+        <literal>json_serialize('{ "a" : 1 } ' RETURNING bytea)</literal>
+        <returnvalue>\x7b20226122203a2031207d20</returnvalue>
+      </para></entry>
+     </row>
      </tbody>
     </tgroup>
    </table>
index bf3a08c5f08d935c90d8e9fe7f5819f396259051..2c62b0c9c84635497dfbf1eed831b5aee6d28d48 100644 (file)
@@ -48,6 +48,7 @@
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/jsonfuncs.h"
 #include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
@@ -2311,6 +2312,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 result */
+                   ExecInitExprRec(linitial(args), state, resv, resnull);
+               }
                else
                {
                    JsonConstructorExprState *jcstate;
@@ -2349,6 +2356,29 @@ 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++)
+                       {
+                           JsonTypeCategory category;
+                           Oid         outfuncid;
+                           Oid         typid = jcstate->arg_types[i];
+
+                           json_categorize_type(typid, is_jsonb,
+                                                &category, &outfuncid);
+
+                           jcstate->arg_type_cache[i].outfuncid = outfuncid;
+                           jcstate->arg_type_cache[i].category = (int) category;
+                       }
+                   }
+
                    ExprEvalPushStep(state, &scratch);
                }
 
index 851946a9272a5dc9779e5cf9168e73bebda3d7fb..24c2b60c62a771bd1725a0c72d334722aef52307 100644 (file)
@@ -4002,6 +4002,47 @@ 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];
+           Oid         outfuncid = jcstate->arg_type_cache[0].outfuncid;
+           JsonTypeCategory category = (JsonTypeCategory)
+               jcstate->arg_type_cache[0].category;
+
+           if (is_jsonb)
+               res = datum_to_jsonb(value, category, outfuncid);
+           else
+               res = datum_to_json(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);
 
index 503d76aae07ebf19dd1b03a7ff0bb1f03031c080..c03f4f23e266b97eddd4356519fd770409dbb0c4 100644 (file)
@@ -3899,6 +3899,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;
index e7134add118814d4c26de43a2c2d0b21a9565b6a..856d5dee0e7e41ed457043967eeecaf21005f941 100644 (file)
@@ -566,7 +566,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>   copy_options
 
 %type <typnam> Typename SimpleTypename ConstTypename
-               GenericType Numeric opt_float
+               GenericType Numeric opt_float JsonType
                Character ConstCharacter
                CharacterWithLength CharacterWithoutLength
                ConstDatetime ConstInterval
@@ -723,6 +723,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
 
@@ -13990,6 +13991,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
@@ -14008,6 +14010,7 @@ ConstTypename:
            | ConstBit                              { $$ = $1; }
            | ConstCharacter                        { $$ = $1; }
            | ConstDatetime                         { $$ = $1; }
+           | JsonType                              { $$ = $1; }
        ;
 
 /*
@@ -14376,6 +14379,13 @@ interval_second:
                }
        ;
 
+JsonType:
+           JSON
+               {
+                   $$ = SystemTypeName("json");
+                   $$->location = @1;
+               }
+       ;
 
 /*****************************************************************************
  *
@@ -15634,7 +15644,36 @@ func_expr_common_subexpr:
                    n->location = @1;
                    $$ = (Node *) n;
                }
-       ;
+           | JSON '(' json_value_expr json_key_uniqueness_constraint_opt ')'
+               {
+                   JsonParseExpr *n = makeNode(JsonParseExpr);
+
+                   n->expr = (JsonValueExpr *) $3;
+                   n->unique_keys = $4;
+                   n->output = NULL;
+                   n->location = @1;
+                   $$ = (Node *) n;
+               }
+           | JSON_SCALAR '(' a_expr ')'
+               {
+                   JsonScalarExpr *n = makeNode(JsonScalarExpr);
+
+                   n->expr = (Expr *) $3;
+                   n->output = NULL;
+                   n->location = @1;
+                   $$ = (Node *) n;
+               }
+           | JSON_SERIALIZE '(' json_value_expr json_returning_clause_opt ')'
+               {
+                   JsonSerializeExpr *n = makeNode(JsonSerializeExpr);
+
+                   n->expr = (JsonValueExpr *) $3;
+                   n->output = (JsonOutput *) $4;
+                   n->location = @1;
+                   $$ = (Node *) n;
+               }
+           ;
+
 
 /*
  * SQL/XML support
@@ -17075,7 +17114,6 @@ unreserved_keyword:
            | INSTEAD
            | INVOKER
            | ISOLATION
-           | JSON
            | KEY
            | KEYS
            | LABEL
@@ -17290,10 +17328,13 @@ col_name_keyword:
            | INT_P
            | INTEGER
            | INTERVAL
+           | JSON
            | JSON_ARRAY
            | JSON_ARRAYAGG
            | JSON_OBJECT
            | JSON_OBJECTAGG
+           | JSON_SCALAR
+           | JSON_SERIALIZE
            | LEAST
            | NATIONAL
            | NCHAR
@@ -17654,6 +17695,8 @@ bare_label_keyword:
            | JSON_ARRAYAGG
            | JSON_OBJECT
            | JSON_OBJECTAGG
+           | JSON_SCALAR
+           | JSON_SERIALIZE
            | KEY
            | KEYS
            | LABEL
index c08c06373a9ad82900c23fba45a189a15ea3b3b2..fed8e4d08972ff984b3f14530df23fc91d231c5f 100644 (file)
@@ -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));
@@ -3204,15 +3220,16 @@ makeJsonByteaToTextConversion(Node *expr, JsonFormat *format, int location)
 
 /*
  * Transform JSON value expression using specified input JSON format or
- * default format otherwise.
+ * default format otherwise, coercing to the targettype if needed.
  *
  * Returned expression is either ve->raw_expr coerced to text (if needed) or
  * a JsonValueExpr with formatted_expr set to the coerced copy of raw_expr
- * if the specified format requires it.
+ * if the specified format and the targettype requires it.
  */
 static Node *
 transformJsonValueExpr(ParseState *pstate, const char *constructName,
-                      JsonValueExpr *ve, JsonFormatType default_format)
+                      JsonValueExpr *ve, JsonFormatType default_format,
+                      Oid targettype)
 {
    Node       *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr);
    Node       *rawexpr;
@@ -3254,12 +3271,14 @@ transformJsonValueExpr(ParseState *pstate, const char *constructName,
    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        only_allow_cast = OidIsValid(targettype);
 
-       if (exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+       if (!only_allow_cast &&
+           exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
            ereport(ERROR,
                    errcode(ERRCODE_DATATYPE_MISMATCH),
                    errmsg(ve->format->format_type == JS_FORMAT_DEFAULT ?
@@ -3275,6 +3294,9 @@ transformJsonValueExpr(ParseState *pstate, const char *constructName,
            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,
@@ -3285,11 +3307,24 @@ transformJsonValueExpr(ParseState *pstate, const char *constructName,
        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;
+
+           /*
+            * Though only allow a cast when the target type is specified by
+            * the caller.
+            */
+           if (only_allow_cast)
+               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;
 
@@ -3590,7 +3625,8 @@ transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor)
            Node       *key = transformExprRecurse(pstate, (Node *) kv->key);
            Node       *val = transformJsonValueExpr(pstate, "JSON_OBJECT()",
                                                     kv->value,
-                                                    JS_FORMAT_DEFAULT);
+                                                    JS_FORMAT_DEFAULT,
+                                                    InvalidOid);
 
            args = lappend(args, key);
            args = lappend(args, val);
@@ -3776,7 +3812,8 @@ transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg)
    key = transformExprRecurse(pstate, (Node *) agg->arg->key);
    val = transformJsonValueExpr(pstate, "JSON_OBJECTAGG()",
                                 agg->arg->value,
-                                JS_FORMAT_DEFAULT);
+                                JS_FORMAT_DEFAULT,
+                                InvalidOid);
    args = list_make2(key, val);
 
    returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
@@ -3834,7 +3871,7 @@ transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg)
 
    arg = transformJsonValueExpr(pstate, "JSON_ARRAYAGG()",
                                 agg->arg,
-                                JS_FORMAT_DEFAULT);
+                                JS_FORMAT_DEFAULT, InvalidOid);
 
    returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
                                               list_make1(arg));
@@ -3882,7 +3919,8 @@ transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor)
            JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc));
            Node       *val = transformJsonValueExpr(pstate, "JSON_ARRAY()",
                                                     jsval,
-                                                    JS_FORMAT_DEFAULT);
+                                                    JS_FORMAT_DEFAULT,
+                                                    InvalidOid);
 
            args = lappend(args, val);
        }
@@ -3963,3 +4001,160 @@ transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred)
    return makeJsonIsPredicate(expr, NULL, pred->item_type,
                               pred->unique_keys, pred->location);
 }
+
+/*
+ * Transform the RETURNING 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.
+ *
+ * JSON() is transformed into a JsonConstructorExpr of type JSCTOR_JSON_PARSE,
+ * which validates the input expression value as JSON.
+ */
+static Node *
+transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr)
+{
+   JsonOutput *output = jsexpr->output;
+   JsonReturning *returning;
+   Node       *arg;
+
+   returning = transformJsonReturning(pstate, output, "JSON()");
+
+   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, "JSON()", jsexpr->expr,
+                                    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.
+ *
+ * JSON_SCALAR() is transformed into a JsonConstructorExpr of type
+ * JSCTOR_JSON_SCALAR, which converts the input SQL scalar value into
+ * a json[b] value.
+ */
+static Node *
+transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *jsexpr)
+{
+   Node       *arg = transformExprRecurse(pstate, (Node *) jsexpr->expr);
+   JsonOutput *output = jsexpr->output;
+   JsonReturning *returning;
+
+   returning = transformJsonReturning(pstate, 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.
+ *
+ * JSON_SERIALIZE() is transformed into a JsonConstructorExpr of type
+ * JSCTOR_JSON_SERIALIZE which converts the input JSON value into a character
+ * or bytea string.
+ */
+static Node *
+transformJsonSerializeExpr(ParseState *pstate, JsonSerializeExpr *expr)
+{
+   JsonReturning *returning;
+   Node       *arg = transformJsonValueExpr(pstate, "JSON_SERIALIZE()",
+                                            expr->expr,
+                                            JS_FORMAT_JSON,
+                                            InvalidOid);
+
+   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);
+}
index 4cca97ff9c18b013ad97336d126b6dce4766d461..57247de363baf2d4c3a3b8122b401aa46329843d 100644 (file)
@@ -1953,6 +1953,18 @@ FigureColnameInternal(Node *node, char **name)
            /* make XMLSERIALIZE act like a regular function */
            *name = "xmlserialize";
            return 2;
+       case T_JsonParseExpr:
+           /* make JSON act like a regular function */
+           *name = "json";
+           return 2;
+       case T_JsonScalarExpr:
+           /* make JSON_SCALAR act like a regular function */
+           *name = "json_scalar";
+           return 2;
+       case T_JsonSerializeExpr:
+           /* make JSON_SERIALIZE act like a regular function */
+           *name = "json_serialize";
+           return 2;
        case T_JsonObjectConstructor:
            /* make JSON_OBJECT act like a regular function */
            *name = "json_object";
index 12402a0637909953ba522a2d9fc46809478943d4..36c45a39780e57cc1a1d589e9299525258df741e 100644 (file)
@@ -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)
index 5ea582a8884cea104fc11d3989bc6148795924d3..9781852b0cb583a7b27fe95ee8c7699c1db77de8 100644 (file)
@@ -33,6 +33,7 @@ typedef struct JsonbInState
 {
    JsonbParseState *parseState;
    JsonbValue *res;
+   bool        unique_keys;
    Node       *escontext;
 } JsonbInState;
 
@@ -45,7 +46,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);
@@ -76,7 +78,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);
 }
 
 /*
@@ -100,7 +102,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);
 }
 
 /*
@@ -147,10 +149,11 @@ jsonb_send(PG_FUNCTION_ARGS)
  * Turns json text string into a jsonb Datum.
  */
 Datum
-jsonb_from_text(text *js)
+jsonb_from_text(text *js, bool unique_keys)
 {
    return jsonb_from_cstring(VARDATA_ANY(js),
                              VARSIZE_ANY_EXHDR(js),
+                             unique_keys,
                              NULL);
 }
 
@@ -247,7 +250,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;
@@ -257,6 +260,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;
 
@@ -293,6 +297,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;
 }
index fcb2f45f623cba335172676007f50938e1417d57..03f2835c3f1aa4241c86f27b06ffc05019932376 100644 (file)
@@ -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,12 @@ get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf)
    if (ctor->unique)
        appendStringInfoString(buf, " WITH UNIQUE KEYS");
 
-   get_json_returning(ctor->returning, buf, true);
+   /*
+    * Append RETURNING clause if needed; JSON() and JSON_SCALAR() don't
+    * support one.
+    */
+   if (ctor->type != JSCTOR_JSON_PARSE && ctor->type != JSCTOR_JSON_SCALAR)
+       get_json_returning(ctor->returning, buf, true);
 }
 
 /*
index d5969e6aea2fc23a6b0d57ce8da5951171706f76..f507b49bb2810b5f17daf38e92f7494e3c824f9b 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202307111
+#define CATALOG_VERSION_NO 202307261
 
 #endif
index efb5c3e098b21ac8e9b3469be4eb24147f99b476..228cdca0f1b5e9a86e9b51afc3e1923cb95ba380 100644 (file)
@@ -1739,6 +1739,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
index e1aadc39cfbe4daf1d6b3c38ef2189efa1196d86..60d72a876b4877ccca1e5770f261fca05bf2c456 100644 (file)