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)
@@ -1613,7 +1613,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_PARSE = 5,
+       JSCTOR_JSON_SCALAR = 6,
+       JSCTOR_JSON_SERIALIZE = 7
 } JsonConstructorType;
 
 /*
index f5b2e61ca527bb16556400975aee85913ef10a0e..5984dcfa4bf61a2a848a02f8b8ffad8cb64cd223 100644 (file)
@@ -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)
index 2ad648d1b8b2c7b8ec248914abe926ab0a343d19..c677ac8ff782e542547e7c1ea1009f7e25bbdcef 100644 (file)
@@ -86,6 +86,6 @@ extern Datum datum_to_json(Datum val, JsonTypeCategory tcategory,
                                                   Oid outfuncoid);
 extern Datum datum_to_jsonb(Datum val, JsonTypeCategory tcategory,
                                                        Oid outfuncoid);
-extern Datum jsonb_from_text(text *js);
+extern Datum jsonb_from_text(text *js, bool unique_keys);
 
 #endif
index a2c49b54f99e308ad2c64b0baa96917cf714dfb5..39221f9ea5dbe965b1df3325705d4923e18932ed 100644 (file)
@@ -196,6 +196,202 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
   printf("Found json=%s\n", json);
 
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( null )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 42 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 42 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{ \"a\" : 1 } ' format json )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 45 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 45 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{ \"a\" : 1 } ' format json encoding UTF8 )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 48 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 48 "sqljson.pgc"
+
+  // error
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '   1   ' :: jsonb )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 51 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 51 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '   1   ' :: json with unique keys ) into json", ECPGt_EOIT, ECPGt_EORT);
+#line 54 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 54 "sqljson.pgc"
+
+  // error
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": 1, \"a\": 2}' )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 57 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 57 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": 1, \"a\": 2}' with unique keys )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 60 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 60 "sqljson.pgc"
+
+  // error
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_scalar ( null )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 63 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 63 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_scalar ( null :: int )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 66 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 66 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_scalar ( 123.45 )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 69 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 69 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_scalar ( true )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 72 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 72 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_scalar ( ' 123.45' )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 75 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 75 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_scalar ( '2020-06-07 01:02:03' :: timestamp )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 78 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 78 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_scalar ( '{}' :: jsonb )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 81 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 81 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_serialize ( null )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 84 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 84 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_serialize ( json ( '{ \"a\" : 1 } ' ) )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 87 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 87 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_serialize ( '{ \"a\" : 1 } ' )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 90 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 90 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_serialize ( '1' format json )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 93 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 93 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_serialize ( '{ \"a\" : 1 } ' returning varchar )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 96 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 96 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_serialize ( '{ \"a\" : 1 } ' returning jsonb )", ECPGt_EOIT, ECPGt_EORT);
+#line 99 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 99 "sqljson.pgc"
+
+  // error
+
   { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "with val ( js ) as ( values ( '{ \"a\": 1, \"b\": [{ \"a\": 1, \"b\": 0, \"a\": 2 }] }' ) ) select js is json \"IS JSON\" , js is not json \"IS NOT JSON\" , js is json value \"IS VALUE\" , js is json object \"IS OBJECT\" , js is json array \"IS ARRAY\" , js is json scalar \"IS SCALAR\" , js is json without unique keys \"WITHOUT UNIQUE\" , js is json with unique keys \"WITH UNIQUE\" from val", ECPGt_EOIT, 
        ECPGt_bool,&(is_json[0]),(long)1,(long)1,sizeof(bool), 
        ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, 
@@ -213,19 +409,19 @@ if (sqlca.sqlcode < 0) sqlprint();}
        ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, 
        ECPGt_bool,&(is_json[7]),(long)1,(long)1,sizeof(bool), 
        ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
-#line 54 "sqljson.pgc"
+#line 114 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
-#line 54 "sqljson.pgc"
+#line 114 "sqljson.pgc"
 
          for (int i = 0; i < sizeof(is_json); i++)
                  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
   { ECPGdisconnect(__LINE__, "CURRENT");
-#line 58 "sqljson.pgc"
+#line 118 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
-#line 58 "sqljson.pgc"
+#line 118 "sqljson.pgc"
 
 
   return 0;
index 1252cb3b66ac2461fdffa67bd469f08a9a975fe2..37a361156f7d845113843b70721c7d20d57901ec 100644 (file)
@@ -65,27 +65,208 @@ SQL error: duplicate JSON key "1" on line 33
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 39: RESULT: {"1": 1} offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 42: query: with val ( js ) as ( values ( '{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }' ) ) select js is json "IS JSON" , js is not json "IS NOT JSON" , js is json value "IS VALUE" , js is json object "IS OBJECT" , js is json array "IS ARRAY" , js is json scalar "IS SCALAR" , js is json without unique keys "WITHOUT UNIQUE" , js is json with unique keys "WITH UNIQUE" from val; with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 42: query: select json ( null ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 42: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_process_output on line 42: correctly got 1 tuples with 8 fields
+[NO_PID]: ecpg_process_output on line 42: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 42: RESULT: t offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 42: RESULT:  offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 42: RESULT: f offset: -1; array: no
+[NO_PID]: raising sqlcode -213 on line 42: null value without indicator on line 42
+[NO_PID]: sqlca: code: -213, state: 22002
+SQL error: null value without indicator on line 42
+[NO_PID]: ecpg_execute on line 45: query: select json ( '{ "a" : 1 } ' format json ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 42: RESULT: t offset: -1; array: no
+[NO_PID]: ecpg_execute on line 45: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 42: RESULT: t offset: -1; array: no
+[NO_PID]: ecpg_process_output on line 45: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 42: RESULT: f offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 45: RESULT: { "a" : 1 }  offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 42: RESULT: f offset: -1; array: no
+[NO_PID]: ecpg_execute on line 48: query: select json ( '{ "a" : 1 } ' format json encoding UTF8 ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 42: RESULT: t offset: -1; array: no
+[NO_PID]: ecpg_execute on line 48: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 42: RESULT: f offset: -1; array: no
+[NO_PID]: ecpg_check_PQresult on line 48: bad response - ERROR:  JSON ENCODING clause is only allowed for bytea input type
+LINE 1: select json ( '{ "a" : 1 } ' format json encoding UTF8 )
+                                     ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): JSON ENCODING clause is only allowed for bytea input type on line 48
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: JSON ENCODING clause is only allowed for bytea input type on line 48
+[NO_PID]: ecpg_execute on line 51: query: select json ( '   1   ' :: jsonb ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 51: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 51: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 51: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 54: query: select json ( '   1   ' :: json with unique keys ) into json; with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 54: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 54: bad response - ERROR:  cannot use non-string types with WITH UNIQUE KEYS clause
+LINE 1: select json ( '   1   ' :: json with unique keys ) into json
+               ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): cannot use non-string types with WITH UNIQUE KEYS clause on line 54
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: cannot use non-string types with WITH UNIQUE KEYS clause on line 54
+[NO_PID]: ecpg_execute on line 57: query: select json ( '{"a": 1, "a": 2}' ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 57: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 57: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 57: RESULT: {"a": 1, "a": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 60: query: select json ( '{"a": 1, "a": 2}' with unique keys ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 60: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 60: bad response - ERROR:  duplicate JSON object key value
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 22030 (sqlcode -400): duplicate JSON object key value on line 60
+[NO_PID]: sqlca: code: -400, state: 22030
+SQL error: duplicate JSON object key value on line 60
+[NO_PID]: ecpg_execute on line 63: query: select json_scalar ( null ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 63: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 63: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 63: RESULT:  offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlcode -213 on line 63: null value without indicator on line 63
+[NO_PID]: sqlca: code: -213, state: 22002
+SQL error: null value without indicator on line 63
+[NO_PID]: ecpg_execute on line 66: query: select json_scalar ( null :: int ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 66: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 66: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 66: RESULT:  offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlcode -213 on line 66: null value without indicator on line 66
+[NO_PID]: sqlca: code: -213, state: 22002
+SQL error: null value without indicator on line 66
+[NO_PID]: ecpg_execute on line 69: query: select json_scalar ( 123.45 ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 69: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 69: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 69: RESULT: 123.45 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 72: query: select json_scalar ( true ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 72: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 72: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 72: RESULT: true offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 75: query: select json_scalar ( ' 123.45' ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 75: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 75: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 75: RESULT: " 123.45" offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 78: query: select json_scalar ( '2020-06-07 01:02:03' :: timestamp ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 78: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 78: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 78: RESULT: "2020-06-07T01:02:03" offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 81: query: select json_scalar ( '{}' :: jsonb ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 81: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 81: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 81: RESULT: {} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 84: query: select json_serialize ( null ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 84: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 84: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 84: RESULT:  offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlcode -213 on line 84: null value without indicator on line 84
+[NO_PID]: sqlca: code: -213, state: 22002
+SQL error: null value without indicator on line 84
+[NO_PID]: ecpg_execute on line 87: query: select json_serialize ( json ( '{ "a" : 1 } ' ) ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 87: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 87: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 87: RESULT: { "a" : 1 }  offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 90: query: select json_serialize ( '{ "a" : 1 } ' ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 90: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 90: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 90: RESULT: { "a" : 1 }  offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 93: query: select json_serialize ( '1' format json ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 93: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 93: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 93: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 96: query: select json_serialize ( '{ "a" : 1 } ' returning varchar ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 96: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 96: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 96: RESULT: { "a" : 1 }  offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 99: query: select json_serialize ( '{ "a" : 1 } ' returning jsonb ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 99: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 99: bad response - ERROR:  cannot use RETURNING type jsonb in JSON_SERIALIZE()
+HINT:  Try returning a string type or bytea.
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): cannot use RETURNING type jsonb in JSON_SERIALIZE() on line 99
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: cannot use RETURNING type jsonb in JSON_SERIALIZE() on line 99
+[NO_PID]: ecpg_execute on line 102: query: with val ( js ) as ( values ( '{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }' ) ) select js is json "IS JSON" , js is not json "IS NOT JSON" , js is json value "IS VALUE" , js is json object "IS OBJECT" , js is json array "IS ARRAY" , js is json scalar "IS SCALAR" , js is json without unique keys "WITHOUT UNIQUE" , js is json with unique keys "WITH UNIQUE" from val; with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 102: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 102: correctly got 1 tuples with 8 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 102: RESULT: t offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 102: RESULT: t offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 102: RESULT: t offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 102: RESULT: t offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
index 558901c4068096bd512d3003af0fc5560dc87995..83f8df13e5a38729fd89f880df236ede6089bfb3 100644 (file)
@@ -4,6 +4,22 @@ Found json=[]
 Found json=[]
 Found json={"1" : 1, "1" : "2"}
 Found json={"1": 1}
+Found json={"1": 1}
+Found json={ "a" : 1 } 
+Found json=1
+Found json={"a": 1, "a": 2}
+Found json={"a": 1, "a": 2}
+Found json={"a": 1, "a": 2}
+Found json=123.45
+Found json=true
+Found json=" 123.45"
+Found json="2020-06-07T01:02:03"
+Found json={}
+Found json={}
+Found json={ "a" : 1 } 
+Found json={ "a" : 1 } 
+Found json=1
+Found json={ "a" : 1 } 
 Found is_json[0]: true
 Found is_json[1]: false
 Found is_json[2]: true
index a00550383447bf5cf9c123be869490901e5d06ed..ddcbcc3b3cb535a51beb39f0e5580abb5ea0dfc4 100644 (file)
@@ -39,6 +39,66 @@ EXEC SQL END DECLARE SECTION;
   EXEC SQL SELECT JSON_OBJECT(1: 1, '2': NULL ABSENT ON NULL WITHOUT UNIQUE RETURNING jsonb) INTO :json;
   printf("Found json=%s\n", json);
 
+  EXEC SQL SELECT JSON(NULL) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON('{ "a" : 1 } ' FORMAT JSON) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON('{ "a" : 1 } ' FORMAT JSON ENCODING UTF8) INTO :json;
+  // error
+
+  EXEC SQL SELECT JSON('   1   '::jsonb) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON('   1   '::json WITH UNIQUE KEYS) INTO json;
+  // error
+
+  EXEC SQL SELECT JSON('{"a": 1, "a": 2}') INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON('{"a": 1, "a": 2}' WITH UNIQUE KEYS) INTO :json;
+  // error
+
+  EXEC SQL SELECT JSON_SCALAR(NULL) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_SCALAR(NULL::int) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_SCALAR(123.45) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_SCALAR(true) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_SCALAR(' 123.45') INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_SCALAR('2020-06-07 01:02:03'::timestamp) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_SCALAR('{}'::jsonb) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_SERIALIZE(NULL) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_SERIALIZE(JSON('{ "a" : 1 } ')) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_SERIALIZE('{ "a" : 1 } ') INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_SERIALIZE('1' FORMAT JSON) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING varchar) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING jsonb);
+  // error
+
   EXEC SQL WITH val (js) AS (VALUES ('{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }'))
          SELECT
          js IS JSON "IS JSON",
index d73c7e2c6cc51bbf5d59c14b0988c4838e384cf8..d5074d73a2c2bc59b7c8b77c2678df3c23aad9ba 100644 (file)
@@ -1,3 +1,293 @@
+-- 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)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123');
+         QUERY PLAN          
+-----------------------------
+ Result
+   Output: JSON('123'::json)
+(2 rows)
+
+SELECT pg_typeof(JSON('123'));
+ pg_typeof 
+-----------
+ json
+(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)
+
+-- 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 +920,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 +942,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);
index 4fd820fd5153affdc5a48d8ec4f859f511bf71c1..e6e20175b005a5dd194c4d8b0bb7ecc39ca578bc 100644 (file)
@@ -1,3 +1,67 @@
+-- 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);
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123');
+SELECT pg_typeof(JSON('123'));
+
+-- 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');
+
+-- 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 +280,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 +294,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);
index 05814136c699375d57fe5ca5eff30982cbabdcc4..11d47294cfa5c71ab9ca666dacf6940c42e39904 100644 (file)
@@ -1274,6 +1274,7 @@ JsonManifestWALRangeField
 JsonObjectAgg
 JsonObjectConstructor
 JsonOutput
+JsonParseExpr
 JsonParseContext
 JsonParseErrorType
 JsonPath
@@ -1295,7 +1296,9 @@ JsonPathParseResult
 JsonPathPredicateCallback
 JsonPathString
 JsonReturning
+JsonScalarExpr
 JsonSemAction
+JsonSerializeExpr
 JsonTokenType
 JsonTransformStringValuesAction
 JsonTypeCategory