SQL/JSON: add standard JSON constructor functions
authorAlvaro Herrera <[email protected]>
Wed, 29 Mar 2023 10:11:36 +0000 (12:11 +0200)
committerAlvaro Herrera <[email protected]>
Wed, 29 Mar 2023 10:11:36 +0000 (12:11 +0200)
This commit introduces the SQL/JSON standard-conforming constructors for
JSON types:

JSON_ARRAY()
JSON_ARRAYAGG()
JSON_OBJECT()
JSON_OBJECTAGG()

Most of the functionality was already present in PostgreSQL-specific
functions, but these include some new functionality such as the ability
to skip or include NULL values, and to allow duplicate keys or throw
error when they are found, as well as the standard specified syntax to
specify output type and format.

Author: Nikita Glukhov <[email protected]>
Author: Teodor Sigaev <[email protected]>
Author: Oleg Bartunov <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Amit Langote <[email protected]>

Reviewers have included (in no particular order) Andres Freund, Alexander
Korotkov, Pavel Stehule, Andrew Alsup, Erik Rijkers, Zihong Yu,
Himanshu Upadhyaya, Daniel Gustafsson, Justin Pryzby.

Discussion: https://postgr.es/m/CAF4Au4w2x-5LTnN_bxky-mq4=WOqsGsxSpENCzHRAzSnEd8+WQ@mail.gmail.com
Discussion: https://postgr.es/m/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru
Discussion: https://postgr.es/m/20220616233130[email protected]
Discussion: https://postgr.es/m/abd9b83b-aa66-f230-3d6d-734817f0995d%40postgresql.org

42 files changed:
doc/src/sgml/func.sgml
src/backend/executor/execExpr.c
src/backend/executor/execExprInterp.c
src/backend/jit/llvm/llvmjit_expr.c
src/backend/jit/llvm/llvmjit_types.c
src/backend/nodes/makefuncs.c
src/backend/nodes/nodeFuncs.c
src/backend/optimizer/util/clauses.c
src/backend/parser/gram.y
src/backend/parser/parse_expr.c
src/backend/parser/parse_target.c
src/backend/parser/parser.c
src/backend/utils/adt/json.c
src/backend/utils/adt/jsonb.c
src/backend/utils/adt/jsonb_util.c
src/backend/utils/adt/ruleutils.c
src/include/catalog/catversion.h
src/include/catalog/pg_aggregate.dat
src/include/catalog/pg_proc.dat
src/include/executor/execExpr.h
src/include/nodes/makefuncs.h
src/include/nodes/parsenodes.h
src/include/nodes/primnodes.h
src/include/parser/kwlist.h
src/include/utils/json.h
src/include/utils/jsonb.h
src/interfaces/ecpg/preproc/parse.pl
src/interfaces/ecpg/preproc/parser.c
src/interfaces/ecpg/test/ecpg_schedule
src/interfaces/ecpg/test/expected/sql-sqljson.c [new file with mode: 0644]
src/interfaces/ecpg/test/expected/sql-sqljson.stderr [new file with mode: 0644]
src/interfaces/ecpg/test/expected/sql-sqljson.stdout [new file with mode: 0644]
src/interfaces/ecpg/test/sql/Makefile
src/interfaces/ecpg/test/sql/meson.build
src/interfaces/ecpg/test/sql/sqljson.pgc [new file with mode: 0644]
src/test/regress/expected/json.out
src/test/regress/expected/opr_sanity.out
src/test/regress/expected/sqljson.out [new file with mode: 0644]
src/test/regress/parallel_schedule
src/test/regress/sql/opr_sanity.sql
src/test/regress/sql/sqljson.sql [new file with mode: 0644]
src/tools/pgindent/typedefs.list

index 974d7be8c2f90e149dcf7060d36cdc4e6c1efacd..38e7f467605c5f1b525198b0b984948290dd1900 100644 (file)
@@ -15229,6 +15229,10 @@ table2-mapping
    <primary>JSON</primary>
    <secondary>functions and operators</secondary>
   </indexterm>
+   <indexterm zone="functions-json">
+    <primary>SQL/JSON</primary>
+    <secondary>functions and expressions</secondary>
+   </indexterm>
 
   <para>
    This section describes:
@@ -15247,6 +15251,42 @@ table2-mapping
    </itemizedlist>
   </para>
 
+  <para>
+   To provide native support for JSON data types within the SQL environment,
+   <productname>PostgreSQL</productname> implements the
+   <firstterm>SQL/JSON data model</firstterm>.
+   This model comprises sequences of items. Each item can hold SQL scalar
+   values, with an additional SQL/JSON null value, and composite data structures
+   that use JSON arrays and objects. The model is a formalization of the implied
+   data model in the JSON specification
+   <ulink url="https://tools.ietf.org/html/rfc7159">RFC 7159</ulink>.
+  </para>
+
+  <para>
+   SQL/JSON allows you to handle JSON data alongside regular SQL data,
+   with transaction support, including:
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Uploading JSON data into the database and storing it in
+     regular SQL columns as character or binary strings.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Generating JSON objects and arrays from relational data.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Querying JSON data using SQL/JSON query functions and
+     SQL/JSON path language expressions.
+    </para>
+   </listitem>
+  </itemizedlist>
+  </para>
+
   <para>
    To learn more about the SQL/JSON standard, see
    <xref linkend="sqltr-19075-6"/>. For details on JSON types
@@ -15677,6 +15717,12 @@ table2-mapping
   <para>
    <xref linkend="functions-json-creation-table"/> shows the functions that are
    available for constructing <type>json</type> and <type>jsonb</type> values.
+   Some functions in this table have a <literal>RETURNING</literal> clause,
+   which specifies the data type returned.  It must be one of <type>json</type>,
+   <type>jsonb</type>, <type>bytea</type>, a character string type (<type>text</type>,
+   <type>char</type>, <type>varchar</type>, or <type>nchar</type>), or a type
+   for which there is a cast from <type>json</type> to that type.
+   By default, the <type>json</type> type is returned.
   </para>
 
   <table id="functions-json-creation-table">
@@ -15760,6 +15806,45 @@ table2-mapping
        </para></entry>
       </row>
 
+      <row>
+       <!--
+           Note that this is barely legible in the output; it looks like a
+           salad of braces and brackets.  It would be better to split it out
+           in multiple lines, but that's surprisingly hard to do in a way that
+           matches in HTML and PDF output.  Other standard SQL/JSON functions
+           have the same problem.
+         -->
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm><primary>json_array</primary></indexterm>
+         <function>json_array</function> (
+         <optional> { <replaceable>value_expression</replaceable> <optional> <literal>FORMAT JSON</literal> </optional> } <optional>, ...</optional> </optional>
+         <optional> { <literal>NULL</literal> | <literal>ABSENT</literal> } <literal>ON NULL</literal> </optional>
+         <optional> <literal>RETURNING</literal> <replaceable>data_type</replaceable> <optional> <literal>FORMAT JSON</literal> <optional> <literal>ENCODING UTF8</literal> </optional> </optional> </optional>)
+        </para>
+        <para role="func_signature">
+         <function>json_array</function> (
+         <optional> <replaceable>query_expression</replaceable> </optional>
+         <optional> <literal>RETURNING</literal> <replaceable>data_type</replaceable> <optional> <literal>FORMAT JSON</literal> <optional> <literal>ENCODING UTF8</literal> </optional> </optional> </optional>)
+        </para>
+        <para>
+         Constructs a JSON array from either a series of
+         <replaceable>value_expression</replaceable> parameters or from the results
+         of <replaceable>query_expression</replaceable>,
+         which must be a SELECT query returning a single column. If
+         <literal>ABSENT ON NULL</literal> is specified, NULL values are ignored.
+         This is always the case if a
+         <replaceable>query_expression</replaceable> is used.
+        </para>
+        <para>
+         <literal>json_array(1,true,json '{"a":null}')</literal>
+         <returnvalue>[1, true, {"a":null}]</returnvalue>
+        </para>
+        <para>
+         <literal>json_array(SELECT * FROM (VALUES(1),(2)) t)</literal>
+         <returnvalue>[1, 2]</returnvalue>
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
@@ -15833,6 +15918,38 @@ table2-mapping
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+         <indexterm><primary>json_object</primary></indexterm>
+         <function>json_object</function> (
+         <optional> { <replaceable>key_expression</replaceable> { <literal>VALUE</literal> | ':' }
+          <replaceable>value_expression</replaceable> <optional> <literal>FORMAT JSON</literal> <optional> <literal>ENCODING UTF8</literal> </optional> </optional> }<optional>, ...</optional> </optional>
+         <optional> { <literal>NULL</literal> | <literal>ABSENT</literal> } <literal>ON NULL</literal> </optional>
+         <optional> { <literal>WITH</literal> | <literal>WITHOUT</literal> } <literal>UNIQUE</literal> <optional> <literal>KEYS</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>
+         Constructs a JSON object of all the key/value pairs given,
+         or an empty object if none are given.
+         <replaceable>key_expression</replaceable> is a scalar expression
+         defining the <acronym>JSON</acronym> key, which is
+         converted to the <type>text</type> type.
+         It cannot be <literal>NULL</literal> nor can it
+         belong to a type that has a cast to the <type>json</type> type.
+         If <literal>WITH UNIQUE KEYS</literal> is specified, there must not
+         be any duplicate <replaceable>key_expression</replaceable>.
+         Any pair for which the <replaceable>value_expression</replaceable>
+         evaluates to <literal>NULL</literal> is omitted from the output
+         if <literal>ABSENT ON NULL</literal> is specified;
+         if <literal>NULL ON NULL</literal> is specified or the clause
+         omitted, the key is included with value <literal>NULL</literal>.
+        </para>
+        <para>
+         <literal>json_object('code' VALUE 'P123', 'title': 'Jaws')</literal>
+         <returnvalue>{"code" : "P123", "title" : "Jaws"}</returnvalue>
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
@@ -20077,6 +20194,28 @@ SELECT NULLIF(value, '(none)') ...
        <entry>No</entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+         <indexterm><primary>json_objectagg</primary></indexterm>
+         <function>json_objectagg</function> (
+         <optional> { <replaceable>key_expression</replaceable> { <literal>VALUE</literal> | ':' } <replaceable>value_expression</replaceable> } </optional>
+         <optional> { <literal>NULL</literal> | <literal>ABSENT</literal> } <literal>ON NULL</literal> </optional>
+        <optional> { <literal>WITH</literal> | <literal>WITHOUT</literal> } <literal>UNIQUE</literal> <optional> <literal>KEYS</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>
+         Behaves like <function>json_object</function><!-- xref -->, but as an
+         aggregate function, so it only takes one
+         <replaceable>key_expression</replaceable> and one
+         <replaceable>value_expression</replaceable> parameter.
+        </para>
+        <para>
+         <literal>SELECT json_objectagg(k:v) FROM (VALUES ('a'::text,current_date),('b',current_date + 1)) AS t(k,v)</literal>
+         <returnvalue>{ "a" : "2022-05-10", "b" : "2022-05-11" }</returnvalue>
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
@@ -20098,9 +20237,122 @@ SELECT NULLIF(value, '(none)') ...
        </para>
        <para>
         Collects all the key/value pairs into a JSON object.  Key arguments
-        are coerced to text; value arguments are converted as
-        per <function>to_json</function> or <function>to_jsonb</function>.
-        Values can be null, but not keys.
+        are coerced to text; value arguments are converted as per
+        <function>to_json</function> or <function>to_jsonb</function>.
+        Values can be null, but keys cannot.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>json_object_agg_strict</primary>
+        </indexterm>
+        <function>json_object_agg_strict</function> (
+         <parameter>key</parameter> <type>"any"</type>,
+         <parameter>value</parameter> <type>"any"</type> )
+        <returnvalue>json</returnvalue>
+       </para>
+       <para role="func_signature">
+        <indexterm>
+         <primary>jsonb_object_agg_strict</primary>
+        </indexterm>
+        <function>jsonb_object_agg_strict</function> (
+         <parameter>key</parameter> <type>"any"</type>,
+         <parameter>value</parameter> <type>"any"</type> )
+        <returnvalue>jsonb</returnvalue>
+       </para>
+       <para>
+        Collects all the key/value pairs into a JSON object.  Key arguments
+        are coerced to text; value arguments are converted as per
+        <function>to_json</function> or <function>to_jsonb</function>.
+        The <parameter>key</parameter> can not be null. If the
+        <parameter>value</parameter> is null then the entry is skipped,
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>json_object_agg_unique</primary>
+        </indexterm>
+        <function>json_object_agg_unique</function> (
+         <parameter>key</parameter> <type>"any"</type>,
+         <parameter>value</parameter> <type>"any"</type> )
+        <returnvalue>json</returnvalue>
+       </para>
+       <para role="func_signature">
+        <indexterm>
+         <primary>jsonb_object_agg_unique</primary>
+        </indexterm>
+        <function>jsonb_object_agg_unique</function> (
+         <parameter>key</parameter> <type>"any"</type>,
+         <parameter>value</parameter> <type>"any"</type> )
+        <returnvalue>jsonb</returnvalue>
+       </para>
+       <para>
+        Collects all the key/value pairs into a JSON object.  Key arguments
+        are coerced to text; value arguments are converted as per
+        <function>to_json</function> or <function>to_jsonb</function>.
+        Values can be null, but keys cannot.
+        If there is a duplicate key an error is thrown.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm><primary>json_arrayagg</primary></indexterm>
+        <function>json_arrayagg</function> (
+        <optional> <replaceable>value_expression</replaceable> </optional>
+        <optional> <literal>ORDER BY</literal> <replaceable>sort_expression</replaceable> </optional>
+        <optional> { <literal>NULL</literal> | <literal>ABSENT</literal> } <literal>ON NULL</literal> </optional>
+        <optional> <literal>RETURNING</literal> <replaceable>data_type</replaceable> <optional> <literal>FORMAT JSON</literal> <optional> <literal>ENCODING UTF8</literal> </optional> </optional> </optional>)
+       </para>
+       <para>
+        Behaves in the same way as <function>json_array</function>
+        but as an aggregate function so it only takes one
+        <replaceable>value_expression</replaceable> parameter.
+        If <literal>ABSENT ON NULL</literal> is specified, any NULL
+        values are omitted.
+        If <literal>ORDER BY</literal> is specified, the elements will
+        appear in the array in that order rather than in the input order.
+       </para>
+       <para>
+        <literal>SELECT json_arrayagg(v) FROM (VALUES(2),(1)) t(v)</literal>
+        <returnvalue>[2, 1]</returnvalue>
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>json_object_agg_unique_strict</primary>
+        </indexterm>
+        <function>json_object_agg_unique_strict</function> (
+         <parameter>key</parameter> <type>"any"</type>,
+         <parameter>value</parameter> <type>"any"</type> )
+        <returnvalue>json</returnvalue>
+       </para>
+       <para role="func_signature">
+        <indexterm>
+         <primary>jsonb_object_agg_unique_strict</primary>
+        </indexterm>
+        <function>jsonb_object_agg_unique_strict</function> (
+         <parameter>key</parameter> <type>"any"</type>,
+         <parameter>value</parameter> <type>"any"</type> )
+        <returnvalue>jsonb</returnvalue>
+       </para>
+       <para>
+        Collects all the key/value pairs into a JSON object.  Key arguments
+        are coerced to text; value arguments are converted as per
+        <function>to_json</function> or <function>to_jsonb</function>.
+        The <parameter>key</parameter> can not be null. If the
+        <parameter>value</parameter> is null then the entry is skipped.
+        If there is a duplicate key an error is thrown.
        </para></entry>
        <entry>No</entry>
       </row>
@@ -20183,6 +20435,29 @@ SELECT NULLIF(value, '(none)') ...
        <entry>No</entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>json_agg_strict</primary>
+        </indexterm>
+        <function>json_agg_strict</function> ( <type>anyelement</type> )
+        <returnvalue>json</returnvalue>
+       </para>
+       <para role="func_signature">
+        <indexterm>
+         <primary>jsonb_agg_strict</primary>
+        </indexterm>
+        <function>jsonb_agg_strict</function> ( <type>anyelement</type> )
+        <returnvalue>jsonb</returnvalue>
+       </para>
+       <para>
+        Collects all the input values, skipping nulls, into a JSON array.
+        Values are converted to JSON as per <function>to_json</function>
+        or <function>to_jsonb</function>.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
@@ -20278,7 +20553,12 @@ SELECT NULLIF(value, '(none)') ...
   <para>
    The aggregate functions <function>array_agg</function>,
    <function>json_agg</function>, <function>jsonb_agg</function>,
+   <function>json_agg_strict</function>, <function>jsonb_agg_strict</function>,
    <function>json_object_agg</function>, <function>jsonb_object_agg</function>,
+   <function>json_object_agg_strict</function>, <function>jsonb_object_agg_strict</function>,
+   <function>json_object_agg_unique</function>, <function>jsonb_object_agg_unique</function>,
+   <function>json_object_agg_unique_strict</function>,
+   <function>jsonb_object_agg_unique_strict</function>,
    <function>string_agg</function>,
    and <function>xmlagg</function>, as well as similar user-defined
    aggregate functions, produce meaningfully different result values
index c61f23c6c186ddac5ff23f416bf9468fbd1f8aa8..6c5a378029256d3bee8e84b6b5136305420c3f21 100644 (file)
@@ -2279,6 +2279,97 @@ ExecInitExprRec(Expr *node, ExprState *state,
                                break;
                        }
 
+               case T_JsonValueExpr:
+                       {
+                               JsonValueExpr *jve = (JsonValueExpr *) node;
+
+                               ExecInitExprRec(jve->raw_expr, state, resv, resnull);
+
+                               if (jve->formatted_expr)
+                               {
+                                       Datum      *innermost_caseval = state->innermost_caseval;
+                                       bool       *innermost_isnull = state->innermost_casenull;
+
+                                       state->innermost_caseval = resv;
+                                       state->innermost_casenull = resnull;
+
+                                       ExecInitExprRec(jve->formatted_expr, state, resv, resnull);
+
+                                       state->innermost_caseval = innermost_caseval;
+                                       state->innermost_casenull = innermost_isnull;
+                               }
+                               break;
+                       }
+
+               case T_JsonConstructorExpr:
+                       {
+                               JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+                               List       *args = ctor->args;
+                               ListCell   *lc;
+                               int                     nargs = list_length(args);
+                               int                     argno = 0;
+
+                               if (ctor->func)
+                               {
+                                       ExecInitExprRec(ctor->func, state, resv, resnull);
+                               }
+                               else
+                               {
+                                       JsonConstructorExprState *jcstate;
+
+                                       jcstate = palloc0(sizeof(JsonConstructorExprState));
+
+                                       scratch.opcode = EEOP_JSON_CONSTRUCTOR;
+                                       scratch.d.json_constructor.jcstate = jcstate;
+
+                                       jcstate->constructor = ctor;
+                                       jcstate->arg_values = (Datum *) palloc(sizeof(Datum) * nargs);
+                                       jcstate->arg_nulls = (bool *) palloc(sizeof(bool) * nargs);
+                                       jcstate->arg_types = (Oid *) palloc(sizeof(Oid) * nargs);
+                                       jcstate->nargs = nargs;
+
+                                       foreach(lc, args)
+                                       {
+                                               Expr       *arg = (Expr *) lfirst(lc);
+
+                                               jcstate->arg_types[argno] = exprType((Node *) arg);
+
+                                               if (IsA(arg, Const))
+                                               {
+                                                       /* Don't evaluate const arguments every round */
+                                                       Const      *con = (Const *) arg;
+
+                                                       jcstate->arg_values[argno] = con->constvalue;
+                                                       jcstate->arg_nulls[argno] = con->constisnull;
+                                               }
+                                               else
+                                               {
+                                                       ExecInitExprRec(arg, state,
+                                                                                       &jcstate->arg_values[argno],
+                                                                                       &jcstate->arg_nulls[argno]);
+                                               }
+                                               argno++;
+                                       }
+
+                                       ExprEvalPushStep(state, &scratch);
+                               }
+
+                               if (ctor->coercion)
+                               {
+                                       Datum      *innermost_caseval = state->innermost_caseval;
+                                       bool       *innermost_isnull = state->innermost_casenull;
+
+                                       state->innermost_caseval = resv;
+                                       state->innermost_casenull = resnull;
+
+                                       ExecInitExprRec(ctor->coercion, state, resv, resnull);
+
+                                       state->innermost_caseval = innermost_caseval;
+                                       state->innermost_casenull = innermost_isnull;
+                               }
+                       }
+                       break;
+
                case T_NullTest:
                        {
                                NullTest   *ntest = (NullTest *) node;
index dd7d1af220a02828f255b7df2bc91bcecdcfd264..a37ba4dd55bc1e7482be3489fd439992ad378659 100644 (file)
@@ -71,6 +71,8 @@
 #include "utils/date.h"
 #include "utils/datum.h"
 #include "utils/expandedrecord.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/timestamp.h"
@@ -474,6 +476,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                &&CASE_EEOP_SCALARARRAYOP,
                &&CASE_EEOP_HASHED_SCALARARRAYOP,
                &&CASE_EEOP_XMLEXPR,
+               &&CASE_EEOP_JSON_CONSTRUCTOR,
                &&CASE_EEOP_AGGREF,
                &&CASE_EEOP_GROUPING_FUNC,
                &&CASE_EEOP_WINDOW_FUNC,
@@ -1511,6 +1514,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                        EEO_NEXT();
                }
 
+               EEO_CASE(EEOP_JSON_CONSTRUCTOR)
+               {
+                       /* too complex for an inline implementation */
+                       ExecEvalJsonConstructor(state, op, econtext);
+                       EEO_NEXT();
+               }
+
                EEO_CASE(EEOP_AGGREF)
                {
                        /*
@@ -4437,3 +4447,43 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
 
        MemoryContextSwitchTo(oldContext);
 }
+
+/*
+ * Evaluate a JSON constructor expression.
+ */
+void
+ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
+                                               ExprContext *econtext)
+{
+       Datum           res;
+       JsonConstructorExprState *jcstate = op->d.json_constructor.jcstate;
+       JsonConstructorExpr *ctor = jcstate->constructor;
+       bool            is_jsonb = ctor->returning->format->format_type == JS_FORMAT_JSONB;
+       bool            isnull = false;
+
+       if (ctor->type == JSCTOR_JSON_ARRAY)
+               res = (is_jsonb ?
+                          jsonb_build_array_worker :
+                          json_build_array_worker) (jcstate->nargs,
+                                                                                jcstate->arg_values,
+                                                                                jcstate->arg_nulls,
+                                                                                jcstate->arg_types,
+                                                                                jcstate->constructor->absent_on_null);
+       else if (ctor->type == JSCTOR_JSON_OBJECT)
+               res = (is_jsonb ?
+                          jsonb_build_object_worker :
+                          json_build_object_worker) (jcstate->nargs,
+                                                                                 jcstate->arg_values,
+                                                                                 jcstate->arg_nulls,
+                                                                                 jcstate->arg_types,
+                                                                                 jcstate->constructor->absent_on_null,
+                                                                                 jcstate->constructor->unique);
+       else
+       {
+               res = (Datum) 0;
+               elog(ERROR, "invalid JsonConstructorExpr type %d", ctor->type);
+       }
+
+       *op->resvalue = res;
+       *op->resnull = isnull;
+}
index 1c722c79552c277ac2d4e0cb5b26b34268776e5b..12d39394b3171563bf60cfec15e31cb2c59896f0 100644 (file)
@@ -1842,6 +1842,12 @@ llvm_compile_expr(ExprState *state)
                                LLVMBuildBr(b, opblocks[opno + 1]);
                                break;
 
+                       case EEOP_JSON_CONSTRUCTOR:
+                               build_EvalXFunc(b, mod, "ExecEvalJsonConstructor",
+                                                               v_state, op, v_econtext);
+                               LLVMBuildBr(b, opblocks[opno + 1]);
+                               break;
+
                        case EEOP_AGGREF:
                                {
                                        LLVMValueRef v_aggno;
index 876fb6402947c033f60cf3b6663967d662ef00f7..315eeb11720943c4de23c22723b84e6c23aefcd2 100644 (file)
@@ -132,6 +132,7 @@ void           *referenced_functions[] =
        ExecEvalSysVar,
        ExecEvalWholeRowVar,
        ExecEvalXmlExpr,
+       ExecEvalJsonConstructor,
        MakeExpandedObjectReadOnlyInternal,
        slot_getmissingattrs,
        slot_getsomeattrs_int,
index 216383ca239de3a2cb631caeb4bbebc28edce47d..23c71528065332fb2ed1323ea3519c0aa8ada3fb 100644 (file)
@@ -19,6 +19,7 @@
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "utils/errcodes.h"
 #include "utils/lsyscache.h"
 
 
@@ -825,3 +826,71 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
        v->va_cols = va_cols;
        return v;
 }
+
+/*
+ * makeJsonFormat -
+ *       creates a JsonFormat node
+ */
+JsonFormat *
+makeJsonFormat(JsonFormatType type, JsonEncoding encoding, int location)
+{
+       JsonFormat *jf = makeNode(JsonFormat);
+
+       jf->format_type = type;
+       jf->encoding = encoding;
+       jf->location = location;
+
+       return jf;
+}
+
+/*
+ * makeJsonValueExpr -
+ *       creates a JsonValueExpr node
+ */
+JsonValueExpr *
+makeJsonValueExpr(Expr *expr, JsonFormat *format)
+{
+       JsonValueExpr *jve = makeNode(JsonValueExpr);
+
+       jve->raw_expr = expr;
+       jve->formatted_expr = NULL;
+       jve->format = format;
+
+       return jve;
+}
+
+/*
+ * makeJsonEncoding -
+ *       converts JSON encoding name to enum JsonEncoding
+ */
+JsonEncoding
+makeJsonEncoding(char *name)
+{
+       if (!pg_strcasecmp(name, "utf8"))
+               return JS_ENC_UTF8;
+       if (!pg_strcasecmp(name, "utf16"))
+               return JS_ENC_UTF16;
+       if (!pg_strcasecmp(name, "utf32"))
+               return JS_ENC_UTF32;
+
+       ereport(ERROR,
+                       errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                       errmsg("unrecognized JSON encoding: %s", name));
+
+       return JS_ENC_DEFAULT;
+}
+
+/*
+ * makeJsonKeyValue -
+ *       creates a JsonKeyValue node
+ */
+Node *
+makeJsonKeyValue(Node *key, Node *value)
+{
+       JsonKeyValue *n = makeNode(JsonKeyValue);
+
+       n->key = (Expr *) key;
+       n->value = castNode(JsonValueExpr, value);
+
+       return (Node *) n;
+}
index dc8415a693cb1bae54a0a53f8236c2b89dd5c609..69907fbcde83da0a65d02cd6e9ddad975cc510f8 100644 (file)
@@ -249,6 +249,18 @@ exprType(const Node *expr)
                case T_PlaceHolderVar:
                        type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
                        break;
+               case T_JsonValueExpr:
+                       {
+                               const JsonValueExpr *jve = (const JsonValueExpr *) expr;
+
+                               type = exprType((Node *)
+                                                               (jve->formatted_expr ? jve->formatted_expr :
+                                                                jve->raw_expr));
+                       }
+                       break;
+               case T_JsonConstructorExpr:
+                       type = ((const JsonConstructorExpr *) expr)->returning->typid;
+                       break;
                default:
                        elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
                        type = InvalidOid;      /* keep compiler quiet */
@@ -479,6 +491,10 @@ exprTypmod(const Node *expr)
                        return ((const SetToDefault *) expr)->typeMod;
                case T_PlaceHolderVar:
                        return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+               case T_JsonValueExpr:
+                       return exprTypmod((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
+               case T_JsonConstructorExpr:
+                       return -1;                      /* XXX maybe expr->returning->typmod? */
                default:
                        break;
        }
@@ -954,6 +970,19 @@ exprCollation(const Node *expr)
                case T_PlaceHolderVar:
                        coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
                        break;
+               case T_JsonValueExpr:
+                       coll = exprCollation((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
+                       break;
+               case T_JsonConstructorExpr:
+                       {
+                               const JsonConstructorExpr *ctor = (const JsonConstructorExpr *) expr;
+
+                               if (ctor->coercion)
+                                       coll = exprCollation((Node *) ctor->coercion);
+                               else
+                                       coll = InvalidOid;
+                       }
+                       break;
                default:
                        elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
                        coll = InvalidOid;      /* keep compiler quiet */
@@ -1161,6 +1190,21 @@ exprSetCollation(Node *expr, Oid collation)
                        /* NextValueExpr's result is an integer type ... */
                        Assert(!OidIsValid(collation)); /* ... so never set a collation */
                        break;
+               case T_JsonValueExpr:
+                       exprSetCollation((Node *) ((JsonValueExpr *) expr)->formatted_expr,
+                                                        collation);
+                       break;
+               case T_JsonConstructorExpr:
+                       {
+                               JsonConstructorExpr *ctor = (JsonConstructorExpr *) expr;
+
+                               if (ctor->coercion)
+                                       exprSetCollation((Node *) ctor->coercion, collation);
+                               else
+                                       Assert(!OidIsValid(collation)); /* result is always a
+                                                                                                        * json[b] type */
+                       }
+                       break;
                default:
                        elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
                        break;
@@ -1603,6 +1647,12 @@ exprLocation(const Node *expr)
                case T_PartitionRangeDatum:
                        loc = ((const PartitionRangeDatum *) expr)->location;
                        break;
+               case T_JsonValueExpr:
+                       loc = exprLocation((Node *) ((const JsonValueExpr *) expr)->raw_expr);
+                       break;
+               case T_JsonConstructorExpr:
+                       loc = ((const JsonConstructorExpr *) expr)->location;
+                       break;
                default:
                        /* for any other node type it's just unknown... */
                        loc = -1;
@@ -2334,6 +2384,28 @@ expression_tree_walker_impl(Node *node,
                                        return true;
                        }
                        break;
+               case T_JsonValueExpr:
+                       {
+                               JsonValueExpr *jve = (JsonValueExpr *) node;
+
+                               if (WALK(jve->raw_expr))
+                                       return true;
+                               if (WALK(jve->formatted_expr))
+                                       return true;
+                       }
+                       break;
+               case T_JsonConstructorExpr:
+                       {
+                               JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+
+                               if (WALK(ctor->args))
+                                       return true;
+                               if (WALK(ctor->func))
+                                       return true;
+                               if (WALK(ctor->coercion))
+                                       return true;
+                       }
+                       break;
                default:
                        elog(ERROR, "unrecognized node type: %d",
                                 (int) nodeTag(node));
@@ -2664,6 +2736,7 @@ expression_tree_mutator_impl(Node *node,
                case T_RangeTblRef:
                case T_SortGroupClause:
                case T_CTESearchClause:
+               case T_JsonFormat:
                        return (Node *) copyObject(node);
                case T_WithCheckOption:
                        {
@@ -3307,6 +3380,41 @@ expression_tree_mutator_impl(Node *node,
                                return (Node *) newnode;
                        }
                        break;
+               case T_JsonReturning:
+                       {
+                               JsonReturning *jr = (JsonReturning *) node;
+                               JsonReturning *newnode;
+
+                               FLATCOPY(newnode, jr, JsonReturning);
+                               MUTATE(newnode->format, jr->format, JsonFormat *);
+
+                               return (Node *) newnode;
+                       }
+               case T_JsonValueExpr:
+                       {
+                               JsonValueExpr *jve = (JsonValueExpr *) node;
+                               JsonValueExpr *newnode;
+
+                               FLATCOPY(newnode, jve, JsonValueExpr);
+                               MUTATE(newnode->raw_expr, jve->raw_expr, Expr *);
+                               MUTATE(newnode->formatted_expr, jve->formatted_expr, Expr *);
+                               MUTATE(newnode->format, jve->format, JsonFormat *);
+
+                               return (Node *) newnode;
+                       }
+               case T_JsonConstructorExpr:
+                       {
+                               JsonConstructorExpr *jve = (JsonConstructorExpr *) node;
+                               JsonConstructorExpr *newnode;
+
+                               FLATCOPY(newnode, jve, JsonConstructorExpr);
+                               MUTATE(newnode->args, jve->args, List *);
+                               MUTATE(newnode->func, jve->func, Expr *);
+                               MUTATE(newnode->coercion, jve->coercion, Expr *);
+                               MUTATE(newnode->returning, jve->returning, JsonReturning *);
+
+                               return (Node *) newnode;
+                       }
                default:
                        elog(ERROR, "unrecognized node type: %d",
                                 (int) nodeTag(node));
@@ -3578,6 +3686,7 @@ raw_expression_tree_walker_impl(Node *node,
                case T_ParamRef:
                case T_A_Const:
                case T_A_Star:
+               case T_JsonFormat:
                        /* primitive node types with no subnodes */
                        break;
                case T_Alias:
@@ -4040,6 +4149,118 @@ raw_expression_tree_walker_impl(Node *node,
                case T_CommonTableExpr:
                        /* search_clause and cycle_clause are not interesting here */
                        return WALK(((CommonTableExpr *) node)->ctequery);
+               case T_JsonReturning:
+                       return WALK(((JsonReturning *) node)->format);
+               case T_JsonValueExpr:
+                       {
+                               JsonValueExpr *jve = (JsonValueExpr *) node;
+
+                               if (WALK(jve->raw_expr))
+                                       return true;
+                               if (WALK(jve->formatted_expr))
+                                       return true;
+                               if (WALK(jve->format))
+                                       return true;
+                       }
+                       break;
+               case T_JsonConstructorExpr:
+                       {
+                               JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+
+                               if (WALK(ctor->args))
+                                       return true;
+                               if (WALK(ctor->func))
+                                       return true;
+                               if (WALK(ctor->coercion))
+                                       return true;
+                               if (WALK(ctor->returning))
+                                       return true;
+                       }
+                       break;
+               case T_JsonOutput:
+                       {
+                               JsonOutput *out = (JsonOutput *) node;
+
+                               if (WALK(out->typeName))
+                                       return true;
+                               if (WALK(out->returning))
+                                       return true;
+                       }
+                       break;
+               case T_JsonKeyValue:
+                       {
+                               JsonKeyValue *jkv = (JsonKeyValue *) node;
+
+                               if (WALK(jkv->key))
+                                       return true;
+                               if (WALK(jkv->value))
+                                       return true;
+                       }
+                       break;
+               case T_JsonObjectConstructor:
+                       {
+                               JsonObjectConstructor *joc = (JsonObjectConstructor *) node;
+
+                               if (WALK(joc->output))
+                                       return true;
+                               if (WALK(joc->exprs))
+                                       return true;
+                       }
+                       break;
+               case T_JsonArrayConstructor:
+                       {
+                               JsonArrayConstructor *jac = (JsonArrayConstructor *) node;
+
+                               if (WALK(jac->output))
+                                       return true;
+                               if (WALK(jac->exprs))
+                                       return true;
+                       }
+                       break;
+               case T_JsonAggConstructor:
+                       {
+                               JsonAggConstructor *ctor = (JsonAggConstructor *) node;
+
+                               if (WALK(ctor->output))
+                                       return true;
+                               if (WALK(ctor->agg_order))
+                                       return true;
+                               if (WALK(ctor->agg_filter))
+                                       return true;
+                               if (WALK(ctor->over))
+                                       return true;
+                       }
+                       break;
+               case T_JsonObjectAgg:
+                       {
+                               JsonObjectAgg *joa = (JsonObjectAgg *) node;
+
+                               if (WALK(joa->constructor))
+                                       return true;
+                               if (WALK(joa->arg))
+                                       return true;
+                       }
+                       break;
+               case T_JsonArrayAgg:
+                       {
+                               JsonArrayAgg *jaa = (JsonArrayAgg *) node;
+
+                               if (WALK(jaa->constructor))
+                                       return true;
+                               if (WALK(jaa->arg))
+                                       return true;
+                       }
+                       break;
+               case T_JsonArrayQueryConstructor:
+                       {
+                               JsonArrayQueryConstructor *jaqc = (JsonArrayQueryConstructor *) node;
+
+                               if (WALK(jaqc->output))
+                                       return true;
+                               if (WALK(jaqc->query))
+                                       return true;
+                       }
+                       break;
                default:
                        elog(ERROR, "unrecognized node type: %d",
                                 (int) nodeTag(node));
index fc245c60e413645e360436cb6e510d3929cfca1e..a9c7bc342eccec6964a98796ee6f92ac3a578a73 100644 (file)
@@ -51,6 +51,8 @@
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/syscache.h"
@@ -383,6 +385,33 @@ contain_mutable_functions_walker(Node *node, void *context)
                                                                context))
                return true;
 
+       if (IsA(node, JsonConstructorExpr))
+       {
+               const JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+               ListCell   *lc;
+               bool            is_jsonb;
+
+               is_jsonb = ctor->returning->format->format_type == JS_FORMAT_JSONB;
+
+               /*
+                * Check argument_type => json[b] conversions specifically.  We still
+                * recurse to check 'args' below, but here we want to specifically
+                * check whether or not the emitted clause would fail to be immutable
+                * because of TimeZone, for example.
+                */
+               foreach(lc, ctor->args)
+               {
+                       Oid                     typid = exprType(lfirst(lc));
+
+                       if (is_jsonb ?
+                               !to_jsonb_is_immutable(typid) :
+                               !to_json_is_immutable(typid))
+                               return true;
+               }
+
+               /* Check all subnodes */
+       }
+
        if (IsA(node, NextValueExpr))
        {
                /* NextValueExpr is volatile */
@@ -2786,6 +2815,32 @@ eval_const_expressions_mutator(Node *node,
                                }
                                break;
                        }
+
+               case T_JsonValueExpr:
+                       {
+                               JsonValueExpr *jve = (JsonValueExpr *) node;
+                               Node       *raw;
+
+                               raw = eval_const_expressions_mutator((Node *) jve->raw_expr,
+                                                                                                        context);
+                               if (raw && IsA(raw, Const))
+                               {
+                                       Node       *formatted;
+                                       Node       *save_case_val = context->case_val;
+
+                                       context->case_val = raw;
+
+                                       formatted = eval_const_expressions_mutator((Node *) jve->formatted_expr,
+                                                                                                                          context);
+
+                                       context->case_val = save_case_val;
+
+                                       if (formatted && IsA(formatted, Const))
+                                               return formatted;
+                               }
+                               break;
+                       }
+
                case T_SubPlan:
                case T_AlternativeSubPlan:
 
index efe88ccf9da7cae72c1439aaebcc31e5905c0244..54c7896a6c649d31c61ddfa42ba9db81268a7d7e 100644 (file)
@@ -644,6 +644,22 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <defelt>         hash_partbound_elem
 
 
+%type <node>           json_format_clause_opt
+                                       json_value_expr
+                                       json_output_clause_opt
+                                       json_name_and_value
+                                       json_aggregate_func
+
+%type <list>           json_name_and_value_list
+                                       json_value_expr_list
+                                       json_array_aggregate_order_by_clause_opt
+
+%type <ival>           json_encoding_clause_opt
+
+%type <boolean>                json_key_uniqueness_constraint_opt
+                                       json_object_constructor_null_clause_opt
+                                       json_array_constructor_null_clause_opt
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -669,7 +685,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  */
 
 /* ordinary key words in alphabetical order */
-%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
+%token <keyword> ABORT_P ABSENT ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
        AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
        ASENSITIVE ASSERTION ASSIGNMENT ASYMMETRIC ATOMIC AT ATTACH ATTRIBUTE AUTHORIZATION
 
@@ -695,7 +711,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
        EXTENSION EXTERNAL EXTRACT
 
        FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
-       FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
+       FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
 
        GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
 
@@ -706,9 +722,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
        INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
        INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
-       JOIN
+       JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG
 
-       KEY
+       KEY KEYS
 
        LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
        LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
@@ -772,9 +788,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * NOT_LA exists so that productions such as NOT LIKE can be given the same
  * precedence as LIKE; otherwise they'd effectively have the same precedence
  * as NOT, at least with respect to their left-hand subexpression.
- * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
+ * FORMAT_LA, NULLS_LA, WITH_LA, and WITHOUT_LA are needed to make the grammar
+ * LALR(1).
  */
-%token         NOT_LA NULLS_LA WITH_LA
+%token         FORMAT_LA NOT_LA NULLS_LA WITH_LA WITHOUT_LA
 
 /*
  * The grammar likewise thinks these tokens are keywords, but they are never
@@ -792,6 +809,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 /* Precedence: lowest to highest */
 %nonassoc      SET                             /* see relation_expr_opt_alias */
+%right         FORMAT
 %left          UNION EXCEPT
 %left          INTERSECT
 %left          OR
@@ -11698,6 +11716,7 @@ utility_option_elem:
 utility_option_name:
                        NonReservedWord                                                 { $$ = $1; }
                        | analyze_keyword                                               { $$ = "analyze"; }
+                       | FORMAT_LA                                                             { $$ = "format"; }
                ;
 
 utility_option_arg:
@@ -15185,6 +15204,16 @@ func_expr: func_application within_group_clause filter_clause over_clause
                                        n->over = $4;
                                        $$ = (Node *) n;
                                }
+                       | json_aggregate_func filter_clause over_clause
+                               {
+                                       JsonAggConstructor *n = IsA($1, JsonObjectAgg) ?
+                                               ((JsonObjectAgg *) $1)->constructor :
+                                               ((JsonArrayAgg *) $1)->constructor;
+
+                                       n->agg_filter = $2;
+                                       n->over = $3;
+                                       $$ = (Node *) $1;
+                               }
                        | func_expr_common_subexpr
                                { $$ = $1; }
                ;
@@ -15198,6 +15227,7 @@ func_expr: func_application within_group_clause filter_clause over_clause
 func_expr_windowless:
                        func_application                                                { $$ = $1; }
                        | func_expr_common_subexpr                              { $$ = $1; }
+                       | json_aggregate_func                                   { $$ = $1; }
                ;
 
 /*
@@ -15543,6 +15573,79 @@ func_expr_common_subexpr:
                                        n->location = @1;
                                        $$ = (Node *) n;
                                }
+                       | JSON_OBJECT '(' func_arg_list ')'
+                               {
+                                       /* Support for legacy (non-standard) json_object() */
+                                       $$ = (Node *) makeFuncCall(SystemFuncName("json_object"),
+                                                                                          $3, COERCE_EXPLICIT_CALL, @1);
+                               }
+                       | JSON_OBJECT '(' json_name_and_value_list
+                               json_object_constructor_null_clause_opt
+                               json_key_uniqueness_constraint_opt
+                               json_output_clause_opt ')'
+                               {
+                                       JsonObjectConstructor *n = makeNode(JsonObjectConstructor);
+
+                                       n->exprs = $3;
+                                       n->absent_on_null = $4;
+                                       n->unique = $5;
+                                       n->output = (JsonOutput *) $6;
+                                       n->location = @1;
+                                       $$ = (Node *) n;
+                               }
+                       | JSON_OBJECT '(' json_output_clause_opt ')'
+                               {
+                                       JsonObjectConstructor *n = makeNode(JsonObjectConstructor);
+
+                                       n->exprs = NULL;
+                                       n->absent_on_null = false;
+                                       n->unique = false;
+                                       n->output = (JsonOutput *) $3;
+                                       n->location = @1;
+                                       $$ = (Node *) n;
+                               }
+                       | JSON_ARRAY '('
+                               json_value_expr_list
+                               json_array_constructor_null_clause_opt
+                               json_output_clause_opt
+                       ')'
+                               {
+                                       JsonArrayConstructor *n = makeNode(JsonArrayConstructor);
+
+                                       n->exprs = $3;
+                                       n->absent_on_null = $4;
+                                       n->output = (JsonOutput *) $5;
+                                       n->location = @1;
+                                       $$ = (Node *) n;
+                               }
+                       | JSON_ARRAY '('
+                               select_no_parens
+                               json_format_clause_opt
+                               /* json_array_constructor_null_clause_opt */
+                               json_output_clause_opt
+                       ')'
+                               {
+                                       JsonArrayQueryConstructor *n = makeNode(JsonArrayQueryConstructor);
+
+                                       n->query = $3;
+                                       n->format = (JsonFormat *) $4;
+                                       n->absent_on_null = true;       /* XXX */
+                                       n->output = (JsonOutput *) $5;
+                                       n->location = @1;
+                                       $$ = (Node *) n;
+                               }
+                       | JSON_ARRAY '('
+                               json_output_clause_opt
+                       ')'
+                               {
+                                       JsonArrayConstructor *n = makeNode(JsonArrayConstructor);
+
+                                       n->exprs = NIL;
+                                       n->absent_on_null = true;
+                                       n->output = (JsonOutput *) $3;
+                                       n->location = @1;
+                                       $$ = (Node *) n;
+                               }
                ;
 
 /*
@@ -16267,6 +16370,132 @@ opt_asymmetric: ASYMMETRIC
                        | /*EMPTY*/
                ;
 
+/* SQL/JSON support */
+json_value_expr:
+                       a_expr json_format_clause_opt
+                       {
+                               $$ = (Node *) makeJsonValueExpr((Expr *) $1, castNode(JsonFormat, $2));
+                       }
+               ;
+
+json_format_clause_opt:
+                       FORMAT_LA JSON json_encoding_clause_opt
+                               {
+                                       $$ = (Node *) makeJsonFormat(JS_FORMAT_JSON, $3, @1);
+                               }
+                       | /* EMPTY */
+                               {
+                                       $$ = (Node *) makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+                               }
+               ;
+
+json_encoding_clause_opt:
+                       ENCODING name                                   { $$ = makeJsonEncoding($2); }
+                       | /* EMPTY */                                   { $$ = JS_ENC_DEFAULT; }
+               ;
+
+json_output_clause_opt:
+                       RETURNING Typename json_format_clause_opt
+                               {
+                                       JsonOutput *n = makeNode(JsonOutput);
+
+                                       n->typeName = $2;
+                                       n->returning = makeNode(JsonReturning);
+                                       n->returning->format = (JsonFormat *) $3;
+                                       $$ = (Node *) n;
+                               }
+                       | /* EMPTY */                                                   { $$ = NULL; }
+               ;
+
+/* KEYS is a noise word here */
+json_key_uniqueness_constraint_opt:
+                       WITH UNIQUE KEYS                                { $$ = true; }
+                       | WITH UNIQUE                               { $$ = true; }
+                       | WITHOUT_LA UNIQUE KEYS                { $$ = false; }
+                       | WITHOUT_LA UNIQUE                         { $$ = false; }
+                       | /* EMPTY */                                   { $$ = false; }
+               ;
+
+json_name_and_value_list:
+                       json_name_and_value
+                               { $$ = list_make1($1); }
+                       | json_name_and_value_list ',' json_name_and_value
+                               { $$ = lappend($1, $3); }
+               ;
+
+json_name_and_value:
+/* Supporting this syntax seems to require major surgery
+                       KEY c_expr VALUE_P json_value_expr
+                               { $$ = makeJsonKeyValue($2, $4); }
+                       |
+*/
+                       c_expr VALUE_P json_value_expr
+                               { $$ = makeJsonKeyValue($1, $3); }
+                       |
+                       a_expr ':' json_value_expr
+                               { $$ = makeJsonKeyValue($1, $3); }
+               ;
+
+/* empty means false for objects, true for arrays */
+json_object_constructor_null_clause_opt:
+                       NULL_P ON NULL_P                                        { $$ = false; }
+                       | ABSENT ON NULL_P                                      { $$ = true; }
+                       | /* EMPTY */                                           { $$ = false; }
+               ;
+
+json_array_constructor_null_clause_opt:
+                       NULL_P ON NULL_P                                                { $$ = false; }
+                       | ABSENT ON NULL_P                                              { $$ = true; }
+                       | /* EMPTY */                                                   { $$ = true; }
+               ;
+
+json_value_expr_list:
+                       json_value_expr                                                         { $$ = list_make1($1); }
+                       | json_value_expr_list ',' json_value_expr      { $$ = lappend($1, $3);}
+               ;
+
+json_aggregate_func:
+                       JSON_OBJECTAGG '('
+                               json_name_and_value
+                               json_object_constructor_null_clause_opt
+                               json_key_uniqueness_constraint_opt
+                               json_output_clause_opt
+                       ')'
+                               {
+                                       JsonObjectAgg *n = makeNode(JsonObjectAgg);
+
+                                       n->arg = (JsonKeyValue *) $3;
+                                       n->absent_on_null = $4;
+                                       n->unique = $5;
+                                       n->constructor = makeNode(JsonAggConstructor);
+                                       n->constructor->output = (JsonOutput *) $6;
+                                       n->constructor->agg_order = NULL;
+                                       n->constructor->location = @1;
+                                       $$ = (Node *) n;
+                               }
+                       | JSON_ARRAYAGG '('
+                               json_value_expr
+                               json_array_aggregate_order_by_clause_opt
+                               json_array_constructor_null_clause_opt
+                               json_output_clause_opt
+                       ')'
+                               {
+                                       JsonArrayAgg *n = makeNode(JsonArrayAgg);
+
+                                       n->arg = (JsonValueExpr *) $3;
+                                       n->absent_on_null = $5;
+                                       n->constructor = makeNode(JsonAggConstructor);
+                                       n->constructor->agg_order = $4;
+                                       n->constructor->output = (JsonOutput *) $6;
+                                       n->constructor->location = @1;
+                                       $$ = (Node *) n;
+                               }
+               ;
+
+json_array_aggregate_order_by_clause_opt:
+                       ORDER BY sortby_list                                    { $$ = $3; }
+                       | /* EMPTY */                                                   { $$ = NIL; }
+               ;
 
 /*****************************************************************************
  *
@@ -16718,6 +16947,7 @@ BareColLabel:   IDENT                                                           { $$ = $1; }
  */
 unreserved_keyword:
                          ABORT_P
+                       | ABSENT
                        | ABSOLUTE_P
                        | ACCESS
                        | ACTION
@@ -16814,6 +17044,7 @@ unreserved_keyword:
                        | FIRST_P
                        | FOLLOWING
                        | FORCE
+                       | FORMAT
                        | FORWARD
                        | FUNCTION
                        | FUNCTIONS
@@ -16846,7 +17077,9 @@ unreserved_keyword:
                        | INSTEAD
                        | INVOKER
                        | ISOLATION
+                       | JSON
                        | KEY
+                       | KEYS
                        | LABEL
                        | LANGUAGE
                        | LARGE_P
@@ -17058,6 +17291,10 @@ col_name_keyword:
                        | INT_P
                        | INTEGER
                        | INTERVAL
+                       | JSON_ARRAY
+                       | JSON_ARRAYAGG
+                       | JSON_OBJECT
+                       | JSON_OBJECTAGG
                        | LEAST
                        | NATIONAL
                        | NCHAR
@@ -17227,6 +17464,7 @@ reserved_keyword:
  */
 bare_label_keyword:
                          ABORT_P
+                       | ABSENT
                        | ABSOLUTE_P
                        | ACCESS
                        | ACTION
@@ -17366,6 +17604,7 @@ bare_label_keyword:
                        | FOLLOWING
                        | FORCE
                        | FOREIGN
+                       | FORMAT
                        | FORWARD
                        | FREEZE
                        | FULL
@@ -17411,7 +17650,13 @@ bare_label_keyword:
                        | IS
                        | ISOLATION
                        | JOIN
+                       | JSON
+                       | JSON_ARRAY
+                       | JSON_ARRAYAGG
+                       | JSON_OBJECT
+                       | JSON_OBJECTAGG
                        | KEY
+                       | KEYS
                        | LABEL
                        | LANGUAGE
                        | LARGE_P
index 233141755225b0f9787207c972af61d9b3c37335..a134878b1e931dd09a7b493055d8363abe75e843 100644 (file)
@@ -15,6 +15,8 @@
 
 #include "postgres.h"
 
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "miscadmin.h"
@@ -34,6 +36,7 @@
 #include "parser/parse_type.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
 #include "utils/xml.h"
@@ -72,6 +75,14 @@ static Node *transformWholeRowRef(ParseState *pstate,
 static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformJsonObjectConstructor(ParseState *pstate,
+                                                                                       JsonObjectConstructor *ctor);
+static Node *transformJsonArrayConstructor(ParseState *pstate,
+                                                                                  JsonArrayConstructor *ctor);
+static Node *transformJsonArrayQueryConstructor(ParseState *pstate,
+                                                                                               JsonArrayQueryConstructor *ctor);
+static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg);
+static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg);
 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,
@@ -294,6 +305,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
                                break;
                        }
 
+               case T_JsonObjectConstructor:
+                       result = transformJsonObjectConstructor(pstate, (JsonObjectConstructor *) expr);
+                       break;
+
+               case T_JsonArrayConstructor:
+                       result = transformJsonArrayConstructor(pstate, (JsonArrayConstructor *) expr);
+                       break;
+
+               case T_JsonArrayQueryConstructor:
+                       result = transformJsonArrayQueryConstructor(pstate, (JsonArrayQueryConstructor *) expr);
+                       break;
+
+               case T_JsonObjectAgg:
+                       result = transformJsonObjectAgg(pstate, (JsonObjectAgg *) expr);
+                       break;
+
+               case T_JsonArrayAgg:
+                       result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr);
+                       break;
+
                default:
                        /* should not reach here */
                        elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3047,3 +3078,742 @@ ParseExprKindName(ParseExprKind exprKind)
        }
        return "unrecognized expression kind";
 }
+
+/*
+ * Make string Const node from JSON encoding name.
+ *
+ * UTF8 is default encoding.
+ */
+static Const *
+getJsonEncodingConst(JsonFormat *format)
+{
+       JsonEncoding encoding;
+       const char *enc;
+       Name            encname = palloc(sizeof(NameData));
+
+       if (!format ||
+               format->format_type == JS_FORMAT_DEFAULT ||
+               format->encoding == JS_ENC_DEFAULT)
+               encoding = JS_ENC_UTF8;
+       else
+               encoding = format->encoding;
+
+       switch (encoding)
+       {
+               case JS_ENC_UTF16:
+                       enc = "UTF16";
+                       break;
+               case JS_ENC_UTF32:
+                       enc = "UTF32";
+                       break;
+               case JS_ENC_UTF8:
+                       enc = "UTF8";
+                       break;
+               default:
+                       elog(ERROR, "invalid JSON encoding: %d", encoding);
+                       break;
+       }
+
+       namestrcpy(encname, enc);
+
+       return makeConst(NAMEOID, -1, InvalidOid, NAMEDATALEN,
+                                        NameGetDatum(encname), false, false);
+}
+
+/*
+ * Make bytea => text conversion using specified JSON format encoding.
+ */
+static Node *
+makeJsonByteaToTextConversion(Node *expr, JsonFormat *format, int location)
+{
+       Const      *encoding = getJsonEncodingConst(format);
+       FuncExpr   *fexpr = makeFuncExpr(F_CONVERT_FROM, TEXTOID,
+                                                                        list_make2(expr, encoding),
+                                                                        InvalidOid, InvalidOid,
+                                                                        COERCE_EXPLICIT_CALL);
+
+       fexpr->location = location;
+
+       return (Node *) fexpr;
+}
+
+/*
+ * Make CaseTestExpr node.
+ */
+static Node *
+makeCaseTestExpr(Node *expr)
+{
+       CaseTestExpr *placeholder = makeNode(CaseTestExpr);
+
+       placeholder->typeId = exprType(expr);
+       placeholder->typeMod = exprTypmod(expr);
+       placeholder->collation = exprCollation(expr);
+
+       return (Node *) placeholder;
+}
+
+/*
+ * Transform JSON value expression using specified input JSON format or
+ * default format otherwise.
+ */
+static Node *
+transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
+                                          JsonFormatType default_format)
+{
+       Node       *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr);
+       Node       *rawexpr;
+       JsonFormatType format;
+       Oid                     exprtype;
+       int                     location;
+       char            typcategory;
+       bool            typispreferred;
+
+       /*
+        * Using JSON_VALUE here is slightly bogus: perhaps we need to be passed a
+        * JsonConstructorType so that we can use one of JSON_OBJECTAGG, etc.
+        */
+       if (exprType(expr) == UNKNOWNOID)
+               expr = coerce_to_specific_type(pstate, expr, TEXTOID, "JSON_VALUE");
+
+       rawexpr = expr;
+       exprtype = exprType(expr);
+       location = exprLocation(expr);
+
+       get_type_category_preferred(exprtype, &typcategory, &typispreferred);
+
+       if (ve->format->format_type != JS_FORMAT_DEFAULT)
+       {
+               if (ve->format->encoding != JS_ENC_DEFAULT && exprtype != BYTEAOID)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_DATATYPE_MISMATCH),
+                                       errmsg("JSON ENCODING clause is only allowed for bytea input type"),
+                                       parser_errposition(pstate, ve->format->location));
+
+               if (exprtype == JSONOID || exprtype == JSONBOID)
+               {
+                       format = JS_FORMAT_DEFAULT; /* do not format json[b] types */
+                       ereport(WARNING,
+                                       errmsg("FORMAT JSON has no effect for json and jsonb types"),
+                                       parser_errposition(pstate, ve->format->location));
+               }
+               else
+                       format = ve->format->format_type;
+       }
+       else if (exprtype == JSONOID || exprtype == JSONBOID)
+               format = JS_FORMAT_DEFAULT; /* do not format json[b] types */
+       else
+               format = default_format;
+
+       if (format != JS_FORMAT_DEFAULT)
+       {
+               Oid                     targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
+               Node       *orig = makeCaseTestExpr(expr);
+               Node       *coerced;
+
+               expr = orig;
+
+               if (exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_DATATYPE_MISMATCH),
+                                       errmsg(ve->format->format_type == JS_FORMAT_DEFAULT ?
+                                                  "cannot use non-string types with implicit FORMAT JSON clause" :
+                                                  "cannot use non-string types with explicit FORMAT JSON clause"),
+                                       parser_errposition(pstate, ve->format->location >= 0 ?
+                                                                          ve->format->location : location));
+
+               /* Convert encoded JSON text from bytea. */
+               if (format == JS_FORMAT_JSON && exprtype == BYTEAOID)
+               {
+                       expr = makeJsonByteaToTextConversion(expr, ve->format, location);
+                       exprtype = TEXTOID;
+               }
+
+               /* Try to coerce to the target type. */
+               coerced = coerce_to_target_type(pstate, expr, exprtype,
+                                                                               targettype, -1,
+                                                                               COERCION_EXPLICIT,
+                                                                               COERCE_EXPLICIT_CAST,
+                                                                               location);
+
+               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);
+
+                       fexpr->location = location;
+
+                       coerced = (Node *) fexpr;
+               }
+
+               if (coerced == orig)
+                       expr = rawexpr;
+               else
+               {
+                       ve = copyObject(ve);
+                       ve->raw_expr = (Expr *) rawexpr;
+                       ve->formatted_expr = (Expr *) coerced;
+
+                       expr = (Node *) ve;
+               }
+       }
+
+       return expr;
+}
+
+/*
+ * Checks specified output format for its applicability to the target type.
+ */
+static void
+checkJsonOutputFormat(ParseState *pstate, const JsonFormat *format,
+                                         Oid targettype, bool allow_format_for_non_strings)
+{
+       if (!allow_format_for_non_strings &&
+               format->format_type != JS_FORMAT_DEFAULT &&
+               (targettype != BYTEAOID &&
+                targettype != JSONOID &&
+                targettype != JSONBOID))
+       {
+               char            typcategory;
+               bool            typispreferred;
+
+               get_type_category_preferred(targettype, &typcategory, &typispreferred);
+
+               if (typcategory != TYPCATEGORY_STRING)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                       parser_errposition(pstate, format->location),
+                                       errmsg("cannot use JSON format with non-string output types"));
+       }
+
+       if (format->format_type == JS_FORMAT_JSON)
+       {
+               JsonEncoding enc = format->encoding != JS_ENC_DEFAULT ?
+               format->encoding : JS_ENC_UTF8;
+
+               if (targettype != BYTEAOID &&
+                       format->encoding != JS_ENC_DEFAULT)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                       parser_errposition(pstate, format->location),
+                                       errmsg("cannot set JSON encoding for non-bytea output types"));
+
+               if (enc != JS_ENC_UTF8)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                       errmsg("unsupported JSON encoding"),
+                                       errhint("Only UTF8 JSON encoding is supported."),
+                                       parser_errposition(pstate, format->location));
+       }
+}
+
+/*
+ * Transform JSON output clause.
+ *
+ * Assigns target type oid and modifier.
+ * Assigns default format or checks specified format for its applicability to
+ * the target type.
+ */
+static JsonReturning *
+transformJsonOutput(ParseState *pstate, const JsonOutput *output,
+                                       bool allow_format)
+{
+       JsonReturning *ret;
+
+       /* if output clause is not specified, make default clause value */
+       if (!output)
+       {
+               ret = makeNode(JsonReturning);
+
+               ret->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+               ret->typid = InvalidOid;
+               ret->typmod = -1;
+
+               return ret;
+       }
+
+       ret = copyObject(output->returning);
+
+       typenameTypeIdAndMod(pstate, output->typeName, &ret->typid, &ret->typmod);
+
+       if (output->typeName->setof)
+               ereport(ERROR,
+                               errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                               errmsg("returning SETOF types is not supported in SQL/JSON functions"));
+
+       if (ret->format->format_type == JS_FORMAT_DEFAULT)
+               /* assign JSONB format when returning jsonb, or JSON format otherwise */
+               ret->format->format_type =
+                       ret->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON;
+       else
+               checkJsonOutputFormat(pstate, ret->format, ret->typid, allow_format);
+
+       return ret;
+}
+
+/*
+ * Transform JSON output clause of JSON constructor functions.
+ *
+ * Derive RETURNING type, if not specified, from argument types.
+ */
+static JsonReturning *
+transformJsonConstructorOutput(ParseState *pstate, JsonOutput *output,
+                                                          List *args)
+{
+       JsonReturning *returning = transformJsonOutput(pstate, output, true);
+
+       if (!OidIsValid(returning->typid))
+       {
+               ListCell   *lc;
+               bool            have_jsonb = false;
+
+               foreach(lc, args)
+               {
+                       Node       *expr = lfirst(lc);
+                       Oid                     typid = exprType(expr);
+
+                       have_jsonb |= typid == JSONBOID;
+
+                       if (have_jsonb)
+                               break;
+               }
+
+               if (have_jsonb)
+               {
+                       returning->typid = JSONBOID;
+                       returning->format->format_type = JS_FORMAT_JSONB;
+               }
+               else
+               {
+                       /* XXX TEXT is default by the standard, but we return JSON */
+                       returning->typid = JSONOID;
+                       returning->format->format_type = JS_FORMAT_JSON;
+               }
+
+               returning->typmod = -1;
+       }
+
+       return returning;
+}
+
+/*
+ * Coerce json[b]-valued function expression to the output type.
+ */
+static Node *
+coerceJsonFuncExpr(ParseState *pstate, Node *expr,
+                                  const JsonReturning *returning, bool report_error)
+{
+       Node       *res;
+       int                     location;
+       Oid                     exprtype = exprType(expr);
+
+       /* if output type is not specified or equals to function type, return */
+       if (!OidIsValid(returning->typid) || returning->typid == exprtype)
+               return expr;
+
+       location = exprLocation(expr);
+
+       if (location < 0)
+               location = returning->format->location;
+
+       /* special case for RETURNING bytea FORMAT json */
+       if (returning->format->format_type == JS_FORMAT_JSON &&
+               returning->typid == BYTEAOID)
+       {
+               /* encode json text into bytea using pg_convert_to() */
+               Node       *texpr = coerce_to_specific_type(pstate, expr, TEXTOID,
+                                                                                                       "JSON_FUNCTION");
+               Const      *enc = getJsonEncodingConst(returning->format);
+               FuncExpr   *fexpr = makeFuncExpr(F_CONVERT_TO, BYTEAOID,
+                                                                                list_make2(texpr, enc),
+                                                                                InvalidOid, InvalidOid,
+                                                                                COERCE_EXPLICIT_CALL);
+
+               fexpr->location = location;
+
+               return (Node *) fexpr;
+       }
+
+       /* try to coerce expression to the output type */
+       res = coerce_to_target_type(pstate, expr, exprtype,
+                                                               returning->typid, returning->typmod,
+       /* XXX throwing errors when casting to char(N) */
+                                                               COERCION_EXPLICIT,
+                                                               COERCE_EXPLICIT_CAST,
+                                                               location);
+
+       if (!res && report_error)
+               ereport(ERROR,
+                               errcode(ERRCODE_CANNOT_COERCE),
+                               errmsg("cannot cast type %s to %s",
+                                          format_type_be(exprtype),
+                                          format_type_be(returning->typid)),
+                               parser_coercion_errposition(pstate, location, expr));
+
+       return res;
+}
+
+static Node *
+makeJsonConstructorExpr(ParseState *pstate, JsonConstructorType type,
+                                               List *args, Expr *fexpr, JsonReturning *returning,
+                                               bool unique, bool absent_on_null, int location)
+{
+       JsonConstructorExpr *jsctor = makeNode(JsonConstructorExpr);
+       Node       *placeholder;
+       Node       *coercion;
+       Oid                     intermediate_typid =
+       returning->format->format_type == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
+
+       jsctor->args = args;
+       jsctor->func = fexpr;
+       jsctor->type = type;
+       jsctor->returning = returning;
+       jsctor->unique = unique;
+       jsctor->absent_on_null = absent_on_null;
+       jsctor->location = location;
+
+       if (fexpr)
+               placeholder = makeCaseTestExpr((Node *) fexpr);
+       else
+       {
+               CaseTestExpr *cte = makeNode(CaseTestExpr);
+
+               cte->typeId = intermediate_typid;
+               cte->typeMod = -1;
+               cte->collation = InvalidOid;
+
+               placeholder = (Node *) cte;
+       }
+
+       coercion = coerceJsonFuncExpr(pstate, placeholder, returning, true);
+
+       if (coercion != placeholder)
+               jsctor->coercion = (Expr *) coercion;
+
+       return (Node *) jsctor;
+}
+
+/*
+ * Transform JSON_OBJECT() constructor.
+ *
+ * JSON_OBJECT() is transformed into json[b]_build_object[_ext]() call
+ * depending on the output JSON format. The first two arguments of
+ * json[b]_build_object_ext() are absent_on_null and check_key_uniqueness.
+ *
+ * Then function call result is coerced to the target type.
+ */
+static Node *
+transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor)
+{
+       JsonReturning *returning;
+       List       *args = NIL;
+
+       /* transform key-value pairs, if any */
+       if (ctor->exprs)
+       {
+               ListCell   *lc;
+
+               /* transform and append key-value arguments */
+               foreach(lc, ctor->exprs)
+               {
+                       JsonKeyValue *kv = castNode(JsonKeyValue, lfirst(lc));
+                       Node       *key = transformExprRecurse(pstate, (Node *) kv->key);
+                       Node       *val = transformJsonValueExpr(pstate, kv->value,
+                                                                                                        JS_FORMAT_DEFAULT);
+
+                       args = lappend(args, key);
+                       args = lappend(args, val);
+               }
+       }
+
+       returning = transformJsonConstructorOutput(pstate, ctor->output, args);
+
+       return makeJsonConstructorExpr(pstate, JSCTOR_JSON_OBJECT, args, NULL,
+                                                                  returning, ctor->unique,
+                                                                  ctor->absent_on_null, ctor->location);
+}
+
+/*
+ * Transform JSON_ARRAY(query [FORMAT] [RETURNING] [ON NULL]) into
+ *  (SELECT  JSON_ARRAYAGG(a  [FORMAT] [RETURNING] [ON NULL]) FROM (query) q(a))
+ */
+static Node *
+transformJsonArrayQueryConstructor(ParseState *pstate,
+                                                                  JsonArrayQueryConstructor *ctor)
+{
+       SubLink    *sublink = makeNode(SubLink);
+       SelectStmt *select = makeNode(SelectStmt);
+       RangeSubselect *range = makeNode(RangeSubselect);
+       Alias      *alias = makeNode(Alias);
+       ResTarget  *target = makeNode(ResTarget);
+       JsonArrayAgg *agg = makeNode(JsonArrayAgg);
+       ColumnRef  *colref = makeNode(ColumnRef);
+       Query      *query;
+       ParseState *qpstate;
+
+       /* Transform query only for counting target list entries. */
+       qpstate = make_parsestate(pstate);
+
+       query = transformStmt(qpstate, ctor->query);
+
+       if (count_nonjunk_tlist_entries(query->targetList) != 1)
+               ereport(ERROR,
+                               errcode(ERRCODE_SYNTAX_ERROR),
+                               errmsg("subquery must return only one column"),
+                               parser_errposition(pstate, ctor->location));
+
+       free_parsestate(qpstate);
+
+       colref->fields = list_make2(makeString(pstrdup("q")),
+                                                               makeString(pstrdup("a")));
+       colref->location = ctor->location;
+
+       agg->arg = makeJsonValueExpr((Expr *) colref, ctor->format);
+       agg->absent_on_null = ctor->absent_on_null;
+       agg->constructor = makeNode(JsonAggConstructor);
+       agg->constructor->agg_order = NIL;
+       agg->constructor->output = ctor->output;
+       agg->constructor->location = ctor->location;
+
+       target->name = NULL;
+       target->indirection = NIL;
+       target->val = (Node *) agg;
+       target->location = ctor->location;
+
+       alias->aliasname = pstrdup("q");
+       alias->colnames = list_make1(makeString(pstrdup("a")));
+
+       range->lateral = false;
+       range->subquery = ctor->query;
+       range->alias = alias;
+
+       select->targetList = list_make1(target);
+       select->fromClause = list_make1(range);
+
+       sublink->subLinkType = EXPR_SUBLINK;
+       sublink->subLinkId = 0;
+       sublink->testexpr = NULL;
+       sublink->operName = NIL;
+       sublink->subselect = (Node *) select;
+       sublink->location = ctor->location;
+
+       return transformExprRecurse(pstate, (Node *) sublink);
+}
+
+/*
+ * Common code for JSON_OBJECTAGG and JSON_ARRAYAGG transformation.
+ */
+static Node *
+transformJsonAggConstructor(ParseState *pstate, JsonAggConstructor *agg_ctor,
+                                                       JsonReturning *returning, List *args,
+                                                       const char *aggfn, Oid aggtype,
+                                                       JsonConstructorType ctor_type,
+                                                       bool unique, bool absent_on_null)
+{
+       Oid                     aggfnoid;
+       Node       *node;
+       Expr       *aggfilter = agg_ctor->agg_filter ? (Expr *)
+       transformWhereClause(pstate, agg_ctor->agg_filter,
+                                                EXPR_KIND_FILTER, "FILTER") : NULL;
+
+       aggfnoid = DatumGetInt32(DirectFunctionCall1(regprocin,
+                                                                                                CStringGetDatum(aggfn)));
+
+       if (agg_ctor->over)
+       {
+               /* window function */
+               WindowFunc *wfunc = makeNode(WindowFunc);
+
+               wfunc->winfnoid = aggfnoid;
+               wfunc->wintype = aggtype;
+               /* wincollid and inputcollid will be set by parse_collate.c */
+               wfunc->args = args;
+               /* winref will be set by transformWindowFuncCall */
+               wfunc->winstar = false;
+               wfunc->winagg = true;
+               wfunc->aggfilter = aggfilter;
+               wfunc->location = agg_ctor->location;
+
+               /*
+                * ordered aggs not allowed in windows yet
+                */
+               if (agg_ctor->agg_order != NIL)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                       errmsg("aggregate ORDER BY is not implemented for window functions"),
+                                       parser_errposition(pstate, agg_ctor->location));
+
+               /* parse_agg.c does additional window-func-specific processing */
+               transformWindowFuncCall(pstate, wfunc, agg_ctor->over);
+
+               node = (Node *) wfunc;
+       }
+       else
+       {
+               Aggref     *aggref = makeNode(Aggref);
+
+               aggref->aggfnoid = aggfnoid;
+               aggref->aggtype = aggtype;
+
+               /* aggcollid and inputcollid will be set by parse_collate.c */
+               aggref->aggtranstype = InvalidOid;      /* will be set by planner */
+               /* aggargtypes will be set by transformAggregateCall */
+               /* aggdirectargs and args will be set by transformAggregateCall */
+               /* aggorder and aggdistinct will be set by transformAggregateCall */
+               aggref->aggfilter = aggfilter;
+               aggref->aggstar = false;
+               aggref->aggvariadic = false;
+               aggref->aggkind = AGGKIND_NORMAL;
+               /* agglevelsup will be set by transformAggregateCall */
+               aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */
+               aggref->location = agg_ctor->location;
+
+               transformAggregateCall(pstate, aggref, args, agg_ctor->agg_order, false);
+
+               node = (Node *) aggref;
+       }
+
+       return makeJsonConstructorExpr(pstate, ctor_type, NIL, (Expr *) node,
+                                                                  returning, unique, absent_on_null,
+                                                                  agg_ctor->location);
+}
+
+/*
+ * Transform JSON_OBJECTAGG() aggregate function.
+ *
+ * JSON_OBJECTAGG() is transformed into
+ * json[b]_objectagg(key, value, absent_on_null, check_unique) call depending on
+ * the output JSON format.  Then the function call result is coerced to the
+ * target output type.
+ */
+static Node *
+transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg)
+{
+       JsonReturning *returning;
+       Node       *key;
+       Node       *val;
+       List       *args;
+       const char *aggfnname;
+       Oid                     aggtype;
+
+       key = transformExprRecurse(pstate, (Node *) agg->arg->key);
+       val = transformJsonValueExpr(pstate, agg->arg->value, JS_FORMAT_DEFAULT);
+       args = list_make2(key, val);
+
+       returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
+                                                                                          args);
+
+       if (returning->format->format_type == JS_FORMAT_JSONB)
+       {
+               if (agg->absent_on_null)
+                       if (agg->unique)
+                               aggfnname = "pg_catalog.jsonb_object_agg_unique_strict";
+                       else
+                               aggfnname = "pg_catalog.jsonb_object_agg_strict";
+               else if (agg->unique)
+                       aggfnname = "pg_catalog.jsonb_object_agg_unique";
+               else
+                       aggfnname = "pg_catalog.jsonb_object_agg";
+
+               aggtype = JSONBOID;
+       }
+       else
+       {
+               if (agg->absent_on_null)
+                       if (agg->unique)
+                               aggfnname = "pg_catalog.json_object_agg_unique_strict";
+                       else
+                               aggfnname = "pg_catalog.json_object_agg_strict";
+               else if (agg->unique)
+                       aggfnname = "pg_catalog.json_object_agg_unique";
+               else
+                       aggfnname = "pg_catalog.json_object_agg";
+
+               aggtype = JSONOID;
+       }
+
+       return transformJsonAggConstructor(pstate, agg->constructor, returning,
+                                                                          args, aggfnname, aggtype,
+                                                                          JSCTOR_JSON_OBJECTAGG,
+                                                                          agg->unique, agg->absent_on_null);
+}
+
+/*
+ * Transform JSON_ARRAYAGG() aggregate function.
+ *
+ * JSON_ARRAYAGG() is transformed into json[b]_agg[_strict]() call depending
+ * on the output JSON format and absent_on_null.  Then the function call result
+ * is coerced to the target output type.
+ */
+static Node *
+transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg)
+{
+       JsonReturning *returning;
+       Node       *arg;
+       const char *aggfnname;
+       Oid                     aggtype;
+
+       arg = transformJsonValueExpr(pstate, agg->arg, JS_FORMAT_DEFAULT);
+
+       returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
+                                                                                          list_make1(arg));
+
+       if (returning->format->format_type == JS_FORMAT_JSONB)
+       {
+               aggfnname = agg->absent_on_null ?
+                       "pg_catalog.jsonb_agg_strict" : "pg_catalog.jsonb_agg";
+               aggtype = JSONBOID;
+       }
+       else
+       {
+               aggfnname = agg->absent_on_null ?
+                       "pg_catalog.json_agg_strict" : "pg_catalog.json_agg";
+               aggtype = JSONOID;
+       }
+
+       return transformJsonAggConstructor(pstate, agg->constructor, returning,
+                                                                          list_make1(arg), aggfnname, aggtype,
+                                                                          JSCTOR_JSON_ARRAYAGG,
+                                                                          false, agg->absent_on_null);
+}
+
+/*
+ * Transform JSON_ARRAY() constructor.
+ *
+ * JSON_ARRAY() is transformed into json[b]_build_array[_ext]() call
+ * depending on the output JSON format. The first argument of
+ * json[b]_build_array_ext() is absent_on_null.
+ *
+ * Then function call result is coerced to the target type.
+ */
+static Node *
+transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor)
+{
+       JsonReturning *returning;
+       List       *args = NIL;
+
+       /* transform element expressions, if any */
+       if (ctor->exprs)
+       {
+               ListCell   *lc;
+
+               /* transform and append element arguments */
+               foreach(lc, ctor->exprs)
+               {
+                       JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc));
+                       Node       *val = transformJsonValueExpr(pstate, jsval,
+                                                                                                        JS_FORMAT_DEFAULT);
+
+                       args = lappend(args, val);
+               }
+       }
+
+       returning = transformJsonConstructorOutput(pstate, ctor->output, args);
+
+       return makeJsonConstructorExpr(pstate, JSCTOR_JSON_ARRAY, args, NULL,
+                                                                  returning, false, ctor->absent_on_null,
+                                                                  ctor->location);
+}
index 25781db5c1d0385d5dee2d96dee782003e2843f3..e77b542fd76500303b2da5edac38fd541f98f25d 100644 (file)
@@ -1907,8 +1907,26 @@ FigureColnameInternal(Node *node, char **name)
                        }
                        break;
                case T_XmlSerialize:
+                       /* make XMLSERIALIZE act like a regular function */
                        *name = "xmlserialize";
                        return 2;
+               case T_JsonObjectConstructor:
+                       /* make JSON_OBJECT act like a regular function */
+                       *name = "json_object";
+                       return 2;
+               case T_JsonArrayConstructor:
+               case T_JsonArrayQueryConstructor:
+                       /* make JSON_ARRAY act like a regular function */
+                       *name = "json_array";
+                       return 2;
+               case T_JsonObjectAgg:
+                       /* make JSON_OBJECTAGG act like a regular function */
+                       *name = "json_objectagg";
+                       return 2;
+               case T_JsonArrayAgg:
+                       /* make JSON_ARRAYAGG act like a regular function */
+                       *name = "json_arrayagg";
+                       return 2;
                default:
                        break;
        }
index aa4dce6ee9c1502ca6e5947da66031e728b983f4..65eb0876575dd75d0740a2997cd1b2382296a016 100644 (file)
@@ -137,6 +137,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
         */
        switch (cur_token)
        {
+               case FORMAT:
+                       cur_token_length = 6;
+                       break;
                case NOT:
                        cur_token_length = 3;
                        break;
@@ -150,6 +153,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
                case USCONST:
                        cur_token_length = strlen(yyextra->core_yy_extra.scanbuf + *llocp);
                        break;
+               case WITHOUT:
+                       cur_token_length = 7;
+                       break;
                default:
                        return cur_token;
        }
@@ -188,6 +194,16 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
        /* Replace cur_token if needed, based on lookahead */
        switch (cur_token)
        {
+               case FORMAT:
+                       /* Replace FORMAT by FORMAT_LA if it's followed by JSON */
+                       switch (next_token)
+                       {
+                               case JSON:
+                                       cur_token = FORMAT_LA;
+                                       break;
+                       }
+                       break;
+
                case NOT:
                        /* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
                        switch (next_token)
@@ -224,6 +240,16 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
                        }
                        break;
 
+               case WITHOUT:
+                       /* Replace WITHOUT by WITHOUT_LA if it's followed by UNIQUE */
+                       switch (next_token)
+                       {
+                               case UNIQUE:
+                                       cur_token = WITHOUT_LA;
+                                       break;
+                       }
+                       break;
+
                case UIDENT:
                case USCONST:
                        /* Look ahead for UESCAPE */
index 06d8bea9cdeb23cb755760896525cb4dc71699df..dcd2bb2234abd52c9601d69d11855edfbbeb3994 100644 (file)
@@ -13,7 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "common/hashfn.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
@@ -42,6 +44,34 @@ typedef enum                                 /* type categories for datum_to_json */
        JSONTYPE_OTHER                          /* all else */
 } JsonTypeCategory;
 
+
+/*
+ * Support for fast key uniqueness checking.
+ *
+ * We maintain a hash table of used keys in JSON objects for fast detection
+ * of duplicates.
+ */
+/* Common context for key uniqueness check */
+typedef struct HTAB *JsonUniqueCheckState;     /* hash table for key names */
+
+/* Hash entry for JsonUniqueCheckState */
+typedef struct JsonUniqueHashEntry
+{
+       const char *key;
+       int                     key_len;
+       int                     object_id;
+} JsonUniqueHashEntry;
+
+/* Context struct for key uniqueness check during JSON building */
+typedef struct JsonUniqueBuilderState
+{
+       JsonUniqueCheckState check; /* unique check */
+       StringInfoData skipped_keys;    /* skipped keys with NULL values */
+       MemoryContext mcxt;                     /* context for saving skipped keys */
+} JsonUniqueBuilderState;
+
+
+/* State struct for JSON aggregation */
 typedef struct JsonAggState
 {
        StringInfo      str;
@@ -49,6 +79,7 @@ typedef struct JsonAggState
        Oid                     key_output_func;
        JsonTypeCategory val_category;
        Oid                     val_output_func;
+       JsonUniqueBuilderState unique_check;
 } JsonAggState;
 
 static void composite_to_json(Datum composite, StringInfo result,
@@ -723,6 +754,48 @@ row_to_json_pretty(PG_FUNCTION_ARGS)
        PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
 }
 
+/*
+ * Is the given type immutable when coming out of a JSON context?
+ *
+ * At present, datetimes are all considered mutable, because they
+ * depend on timezone.  XXX we should also drill down into objects
+ * and arrays, but do not.
+ */
+bool
+to_json_is_immutable(Oid typoid)
+{
+       JsonTypeCategory tcategory;
+       Oid                     outfuncoid;
+
+       json_categorize_type(typoid, &tcategory, &outfuncoid);
+
+       switch (tcategory)
+       {
+               case JSONTYPE_BOOL:
+               case JSONTYPE_JSON:
+               case JSONTYPE_NULL:
+                       return true;
+
+               case JSONTYPE_DATE:
+               case JSONTYPE_TIMESTAMP:
+               case JSONTYPE_TIMESTAMPTZ:
+                       return false;
+
+               case JSONTYPE_ARRAY:
+                       return false;           /* TODO recurse into elements */
+
+               case JSONTYPE_COMPOSITE:
+                       return false;           /* TODO recurse into fields */
+
+               case JSONTYPE_NUMERIC:
+               case JSONTYPE_CAST:
+               case JSONTYPE_OTHER:
+                       return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE;
+       }
+
+       return false;                           /* not reached */
+}
+
 /*
  * SQL function to_json(anyvalue)
  */
@@ -755,8 +828,8 @@ to_json(PG_FUNCTION_ARGS)
  *
  * aggregate input column as a json array value.
  */
-Datum
-json_agg_transfn(PG_FUNCTION_ARGS)
+static Datum
+json_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null)
 {
        MemoryContext aggcontext,
                                oldcontext;
@@ -796,9 +869,14 @@ json_agg_transfn(PG_FUNCTION_ARGS)
        else
        {
                state = (JsonAggState *) PG_GETARG_POINTER(0);
-               appendStringInfoString(state->str, ", ");
        }
 
+       if (absent_on_null && PG_ARGISNULL(1))
+               PG_RETURN_POINTER(state);
+
+       if (state->str->len > 1)
+               appendStringInfoString(state->str, ", ");
+
        /* fast path for NULLs */
        if (PG_ARGISNULL(1))
        {
@@ -810,7 +888,7 @@ json_agg_transfn(PG_FUNCTION_ARGS)
        val = PG_GETARG_DATUM(1);
 
        /* add some whitespace if structured type and not first item */
-       if (!PG_ARGISNULL(0) &&
+       if (!PG_ARGISNULL(0) && state->str->len > 1 &&
                (state->val_category == JSONTYPE_ARRAY ||
                 state->val_category == JSONTYPE_COMPOSITE))
        {
@@ -828,6 +906,25 @@ json_agg_transfn(PG_FUNCTION_ARGS)
        PG_RETURN_POINTER(state);
 }
 
+
+/*
+ * json_agg aggregate function
+ */
+Datum
+json_agg_transfn(PG_FUNCTION_ARGS)
+{
+       return json_agg_transfn_worker(fcinfo, false);
+}
+
+/*
+ * json_agg_strict aggregate function
+ */
+Datum
+json_agg_strict_transfn(PG_FUNCTION_ARGS)
+{
+       return json_agg_transfn_worker(fcinfo, true);
+}
+
 /*
  * json_agg final function
  */
@@ -851,18 +948,120 @@ json_agg_finalfn(PG_FUNCTION_ARGS)
        PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, "]"));
 }
 
+/* Functions implementing hash table for key uniqueness check */
+static uint32
+json_unique_hash(const void *key, Size keysize)
+{
+       const JsonUniqueHashEntry *entry = (JsonUniqueHashEntry *) key;
+       uint32          hash = hash_bytes_uint32(entry->object_id);
+
+       hash ^= hash_bytes((const unsigned char *) entry->key, entry->key_len);
+
+       return DatumGetUInt32(hash);
+}
+
+static int
+json_unique_hash_match(const void *key1, const void *key2, Size keysize)
+{
+       const JsonUniqueHashEntry *entry1 = (const JsonUniqueHashEntry *) key1;
+       const JsonUniqueHashEntry *entry2 = (const JsonUniqueHashEntry *) key2;
+
+       if (entry1->object_id != entry2->object_id)
+               return entry1->object_id > entry2->object_id ? 1 : -1;
+
+       if (entry1->key_len != entry2->key_len)
+               return entry1->key_len > entry2->key_len ? 1 : -1;
+
+       return strncmp(entry1->key, entry2->key, entry1->key_len);
+}
+
+/*
+ * Uniqueness detection support.
+ *
+ * In order to detect uniqueness during building or parsing of a JSON
+ * object, we maintain a hash table of key names already seen.
+ */
+static void
+json_unique_check_init(JsonUniqueCheckState *cxt)
+{
+       HASHCTL         ctl;
+
+       memset(&ctl, 0, sizeof(ctl));
+       ctl.keysize = sizeof(JsonUniqueHashEntry);
+       ctl.entrysize = sizeof(JsonUniqueHashEntry);
+       ctl.hcxt = CurrentMemoryContext;
+       ctl.hash = json_unique_hash;
+       ctl.match = json_unique_hash_match;
+
+       *cxt = hash_create("json object hashtable",
+                                          32,
+                                          &ctl,
+                                          HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE);
+}
+
+static void
+json_unique_builder_init(JsonUniqueBuilderState *cxt)
+{
+       json_unique_check_init(&cxt->check);
+       cxt->mcxt = CurrentMemoryContext;
+       cxt->skipped_keys.data = NULL;
+}
+
+static bool
+json_unique_check_key(JsonUniqueCheckState *cxt, const char *key, int object_id)
+{
+       JsonUniqueHashEntry entry;
+       bool            found;
+
+       entry.key = key;
+       entry.key_len = strlen(key);
+       entry.object_id = object_id;
+
+       (void) hash_search(*cxt, &entry, HASH_ENTER, &found);
+
+       return !found;
+}
+
+/*
+ * On-demand initialization of a throwaway StringInfo.  This is used to
+ * read a key name that we don't need to store in the output object, for
+ * duplicate key detection when the value is NULL.
+ */
+static StringInfo
+json_unique_builder_get_throwawaybuf(JsonUniqueBuilderState *cxt)
+{
+       StringInfo      out = &cxt->skipped_keys;
+
+       if (!out->data)
+       {
+               MemoryContext oldcxt = MemoryContextSwitchTo(cxt->mcxt);
+
+               initStringInfo(out);
+               MemoryContextSwitchTo(oldcxt);
+       }
+       else
+               /* Just reset the string to empty */
+               out->len = 0;
+
+       return out;
+}
+
 /*
  * json_object_agg transition function.
  *
  * aggregate two input columns as a single json object value.
  */
-Datum
-json_object_agg_transfn(PG_FUNCTION_ARGS)
+static Datum
+json_object_agg_transfn_worker(FunctionCallInfo fcinfo,
+                                                          bool absent_on_null, bool unique_keys)
 {
        MemoryContext aggcontext,
                                oldcontext;
        JsonAggState *state;
+       StringInfo      out;
        Datum           arg;
+       bool            skip;
+       int                     key_offset;
 
        if (!AggCheckCallContext(fcinfo, &aggcontext))
        {
@@ -877,12 +1076,16 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
                /*
                 * Make the StringInfo in a context where it will persist for the
                 * duration of the aggregate call. Switching context is only needed
-                * for this initial step, as the StringInfo routines make sure they
-                * use the right context to enlarge the object if necessary.
+                * for this initial step, as the StringInfo and dynahash routines make
+                * sure they use the right context to enlarge the object if necessary.
                 */
                oldcontext = MemoryContextSwitchTo(aggcontext);
                state = (JsonAggState *) palloc(sizeof(JsonAggState));
                state->str = makeStringInfo();
+               if (unique_keys)
+                       json_unique_builder_init(&state->unique_check);
+               else
+                       memset(&state->unique_check, 0, sizeof(state->unique_check));
                MemoryContextSwitchTo(oldcontext);
 
                arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
@@ -910,7 +1113,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
        else
        {
                state = (JsonAggState *) PG_GETARG_POINTER(0);
-               appendStringInfoString(state->str, ", ");
        }
 
        /*
@@ -923,14 +1125,56 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
 
        if (PG_ARGISNULL(1))
                ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                                errmsg("field name must not be null")));
+                               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                errmsg("null value not allowed for object key")));
+
+       /* Skip null values if absent_on_null */
+       skip = absent_on_null && PG_ARGISNULL(2);
+
+       if (skip)
+       {
+               /*
+                * We got a NULL value and we're not storing those; if we're not
+                * testing key uniqueness, we're done.  If we are, use the throwaway
+                * buffer to store the key name so that we can check it.
+                */
+               if (!unique_keys)
+                       PG_RETURN_POINTER(state);
+
+               out = json_unique_builder_get_throwawaybuf(&state->unique_check);
+       }
+       else
+       {
+               out = state->str;
+
+               /*
+                * Append comma delimiter only if we have already output some fields
+                * after the initial string "{ ".
+                */
+               if (out->len > 2)
+                       appendStringInfoString(out, ", ");
+       }
 
        arg = PG_GETARG_DATUM(1);
 
-       datum_to_json(arg, false, state->str, state->key_category,
+       key_offset = out->len;
+
+       datum_to_json(arg, false, out, state->key_category,
                                  state->key_output_func, true);
 
+       if (unique_keys)
+       {
+               const char *key = &out->data[key_offset];
+
+               if (!json_unique_check_key(&state->unique_check.check, key, 0))
+                       ereport(ERROR,
+                                       errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE),
+                                       errmsg("duplicate JSON key %s", key));
+
+               if (skip)
+                       PG_RETURN_POINTER(state);
+       }
+
        appendStringInfoString(state->str, " : ");
 
        if (PG_ARGISNULL(2))
@@ -944,6 +1188,42 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
        PG_RETURN_POINTER(state);
 }
 
+/*
+ * json_object_agg aggregate function
+ */
+Datum
+json_object_agg_transfn(PG_FUNCTION_ARGS)
+{
+       return json_object_agg_transfn_worker(fcinfo, false, false);
+}
+
+/*
+ * json_object_agg_strict aggregate function
+ */
+Datum
+json_object_agg_strict_transfn(PG_FUNCTION_ARGS)
+{
+       return json_object_agg_transfn_worker(fcinfo, true, false);
+}
+
+/*
+ * json_object_agg_unique aggregate function
+ */
+Datum
+json_object_agg_unique_transfn(PG_FUNCTION_ARGS)
+{
+       return json_object_agg_transfn_worker(fcinfo, false, true);
+}
+
+/*
+ * json_object_agg_unique_strict aggregate function
+ */
+Datum
+json_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS)
+{
+       return json_object_agg_transfn_worker(fcinfo, true, true);
+}
+
 /*
  * json_object_agg final function.
  */
@@ -985,25 +1265,14 @@ catenate_stringinfo_string(StringInfo buffer, const char *addon)
        return result;
 }
 
-/*
- * SQL function json_build_object(variadic "any")
- */
 Datum
-json_build_object(PG_FUNCTION_ARGS)
+json_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types,
+                                                bool absent_on_null, bool unique_keys)
 {
-       int                     nargs;
        int                     i;
        const char *sep = "";
        StringInfo      result;
-       Datum      *args;
-       bool       *nulls;
-       Oid                *types;
-
-       /* fetch argument values to build the object */
-       nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls);
-
-       if (nargs < 0)
-               PG_RETURN_NULL();
+       JsonUniqueBuilderState unique_check;
 
        if (nargs % 2 != 0)
                ereport(ERROR,
@@ -1017,19 +1286,57 @@ json_build_object(PG_FUNCTION_ARGS)
 
        appendStringInfoChar(result, '{');
 
+       if (unique_keys)
+               json_unique_builder_init(&unique_check);
+
        for (i = 0; i < nargs; i += 2)
        {
-               appendStringInfoString(result, sep);
-               sep = ", ";
+               StringInfo      out;
+               bool            skip;
+               int                     key_offset;
+
+               /* Skip null values if absent_on_null */
+               skip = absent_on_null && nulls[i + 1];
+
+               if (skip)
+               {
+                       /* If key uniqueness check is needed we must save skipped keys */
+                       if (!unique_keys)
+                               continue;
+
+                       out = json_unique_builder_get_throwawaybuf(&unique_check);
+               }
+               else
+               {
+                       appendStringInfoString(result, sep);
+                       sep = ", ";
+                       out = result;
+               }
 
                /* process key */
                if (nulls[i])
                        ereport(ERROR,
-                                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                                        errmsg("argument %d cannot be null", i + 1),
-                                        errhint("Object keys should be text.")));
+                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                        errmsg("null value not allowed for object key")));
+
+               /* save key offset before appending it */
+               key_offset = out->len;
 
-               add_json(args[i], false, result, types[i], true);
+               add_json(args[i], false, out, types[i], true);
+
+               if (unique_keys)
+               {
+                       /* check key uniqueness after key appending */
+                       const char *key = &out->data[key_offset];
+
+                       if (!json_unique_check_key(&unique_check.check, key, 0))
+                               ereport(ERROR,
+                                               errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE),
+                                               errmsg("duplicate JSON key %s", key));
+
+                       if (skip)
+                               continue;
+               }
 
                appendStringInfoString(result, " : ");
 
@@ -1039,7 +1346,27 @@ json_build_object(PG_FUNCTION_ARGS)
 
        appendStringInfoChar(result, '}');
 
-       PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
+       return PointerGetDatum(cstring_to_text_with_len(result->data, result->len));
+}
+
+/*
+ * SQL function json_build_object(variadic "any")
+ */
+Datum
+json_build_object(PG_FUNCTION_ARGS)
+{
+       Datum      *args;
+       bool       *nulls;
+       Oid                *types;
+
+       /* build argument values to build the object */
+       int                     nargs = extract_variadic_args(fcinfo, 0, true,
+                                                                                         &args, &types, &nulls);
+
+       if (nargs < 0)
+               PG_RETURN_NULL();
+
+       PG_RETURN_DATUM(json_build_object_worker(nargs, args, nulls, types, false, false));
 }
 
 /*
@@ -1051,25 +1378,13 @@ json_build_object_noargs(PG_FUNCTION_ARGS)
        PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
 }
 
-/*
- * SQL function json_build_array(variadic "any")
- */
 Datum
-json_build_array(PG_FUNCTION_ARGS)
+json_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types,
+                                               bool absent_on_null)
 {
-       int                     nargs;
        int                     i;
        const char *sep = "";
        StringInfo      result;
-       Datum      *args;
-       bool       *nulls;
-       Oid                *types;
-
-       /* fetch argument values to build the array */
-       nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls);
-
-       if (nargs < 0)
-               PG_RETURN_NULL();
 
        result = makeStringInfo();
 
@@ -1077,6 +1392,9 @@ json_build_array(PG_FUNCTION_ARGS)
 
        for (i = 0; i < nargs; i++)
        {
+               if (absent_on_null && nulls[i])
+                       continue;
+
                appendStringInfoString(result, sep);
                sep = ", ";
                add_json(args[i], nulls[i], result, types[i], false);
@@ -1084,7 +1402,27 @@ json_build_array(PG_FUNCTION_ARGS)
 
        appendStringInfoChar(result, ']');
 
-       PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
+       return PointerGetDatum(cstring_to_text_with_len(result->data, result->len));
+}
+
+/*
+ * SQL function json_build_array(variadic "any")
+ */
+Datum
+json_build_array(PG_FUNCTION_ARGS)
+{
+       Datum      *args;
+       bool       *nulls;
+       Oid                *types;
+
+       /* build argument values to build the object */
+       int                     nargs = extract_variadic_args(fcinfo, 0, true,
+                                                                                         &args, &types, &nulls);
+
+       if (nargs < 0)
+               PG_RETURN_NULL();
+
+       PG_RETURN_DATUM(json_build_array_worker(nargs, args, nulls, types, false));
 }
 
 /*
index 0539f41c172ee4946bc4ee4fdc44c9538b676384..cf43c3f2ded19aabd2a913ab914f84d6c70d3afc 100644 (file)
@@ -14,6 +14,7 @@
 
 #include "access/htup_details.h"
 #include "access/transam.h"
+#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -1149,6 +1150,49 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result,
        datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar);
 }
 
+/*
+ * Is the given type immutable when coming out of a JSONB context?
+ *
+ * At present, datetimes are all considered mutable, because they
+ * depend on timezone.  XXX we should also drill down into objects and
+ * arrays, but do not.
+ */
+bool
+to_jsonb_is_immutable(Oid typoid)
+{
+       JsonbTypeCategory tcategory;
+       Oid                     outfuncoid;
+
+       jsonb_categorize_type(typoid, &tcategory, &outfuncoid);
+
+       switch (tcategory)
+       {
+               case JSONBTYPE_NULL:
+               case JSONBTYPE_BOOL:
+               case JSONBTYPE_JSON:
+               case JSONBTYPE_JSONB:
+                       return true;
+
+               case JSONBTYPE_DATE:
+               case JSONBTYPE_TIMESTAMP:
+               case JSONBTYPE_TIMESTAMPTZ:
+                       return false;
+
+               case JSONBTYPE_ARRAY:
+                       return false;           /* TODO recurse into elements */
+
+               case JSONBTYPE_COMPOSITE:
+                       return false;           /* TODO recurse into fields */
+
+               case JSONBTYPE_NUMERIC:
+               case JSONBTYPE_JSONCAST:
+               case JSONBTYPE_OTHER:
+                       return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE;
+       }
+
+       return false;                           /* not reached */
+}
+
 /*
  * SQL function to_jsonb(anyvalue)
  */
@@ -1176,24 +1220,12 @@ to_jsonb(PG_FUNCTION_ARGS)
        PG_RETURN_POINTER(JsonbValueToJsonb(result.res));
 }
 
-/*
- * SQL function jsonb_build_object(variadic "any")
- */
 Datum
-jsonb_build_object(PG_FUNCTION_ARGS)
+jsonb_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types,
+                                                 bool absent_on_null, bool unique_keys)
 {
-       int                     nargs;
        int                     i;
        JsonbInState result;
-       Datum      *args;
-       bool       *nulls;
-       Oid                *types;
-
-       /* build argument values to build the object */
-       nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
-
-       if (nargs < 0)
-               PG_RETURN_NULL();
 
        if (nargs % 2 != 0)
                ereport(ERROR,
@@ -1206,15 +1238,26 @@ jsonb_build_object(PG_FUNCTION_ARGS)
        memset(&result, 0, sizeof(JsonbInState));
 
        result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL);
+       result.parseState->unique_keys = unique_keys;
+       result.parseState->skip_nulls = absent_on_null;
 
        for (i = 0; i < nargs; i += 2)
        {
                /* process key */
+               bool            skip;
+
                if (nulls[i])
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                         errmsg("argument %d: key must not be null", i + 1)));
 
+               /* skip null values if absent_on_null */
+               skip = absent_on_null && nulls[i + 1];
+
+               /* we need to save skipped keys for the key uniqueness check */
+               if (skip && !unique_keys)
+                       continue;
+
                add_jsonb(args[i], false, &result, types[i], true);
 
                /* process value */
@@ -1223,7 +1266,27 @@ jsonb_build_object(PG_FUNCTION_ARGS)
 
        result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL);
 
-       PG_RETURN_POINTER(JsonbValueToJsonb(result.res));
+       return JsonbPGetDatum(JsonbValueToJsonb(result.res));
+}
+
+/*
+ * SQL function jsonb_build_object(variadic "any")
+ */
+Datum
+jsonb_build_object(PG_FUNCTION_ARGS)
+{
+       Datum      *args;
+       bool       *nulls;
+       Oid                *types;
+
+       /* build argument values to build the object */
+       int                     nargs = extract_variadic_args(fcinfo, 0, true,
+                                                                                         &args, &types, &nulls);
+
+       if (nargs < 0)
+               PG_RETURN_NULL();
+
+       PG_RETURN_DATUM(jsonb_build_object_worker(nargs, args, nulls, types, false, false));
 }
 
 /*
@@ -1242,37 +1305,51 @@ jsonb_build_object_noargs(PG_FUNCTION_ARGS)
        PG_RETURN_POINTER(JsonbValueToJsonb(result.res));
 }
 
-/*
- * SQL function jsonb_build_array(variadic "any")
- */
 Datum
-jsonb_build_array(PG_FUNCTION_ARGS)
+jsonb_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types,
+                                                bool absent_on_null)
 {
-       int                     nargs;
        int                     i;
        JsonbInState result;
-       Datum      *args;
-       bool       *nulls;
-       Oid                *types;
-
-       /* build argument values to build the array */
-       nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
-
-       if (nargs < 0)
-               PG_RETURN_NULL();
 
        memset(&result, 0, sizeof(JsonbInState));
 
        result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL);
 
        for (i = 0; i < nargs; i++)
+       {
+               if (absent_on_null && nulls[i])
+                       continue;
+
                add_jsonb(args[i], nulls[i], &result, types[i], false);
+       }
 
        result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL);
 
-       PG_RETURN_POINTER(JsonbValueToJsonb(result.res));
+       return JsonbPGetDatum(JsonbValueToJsonb(result.res));
 }
 
+/*
+ * SQL function jsonb_build_array(variadic "any")
+ */
+Datum
+jsonb_build_array(PG_FUNCTION_ARGS)
+{
+       Datum      *args;
+       bool       *nulls;
+       Oid                *types;
+
+       /* build argument values to build the object */
+       int                     nargs = extract_variadic_args(fcinfo, 0, true,
+                                                                                         &args, &types, &nulls);
+
+       if (nargs < 0)
+               PG_RETURN_NULL();
+
+       PG_RETURN_DATUM(jsonb_build_array_worker(nargs, args, nulls, types, false));
+}
+
+
 /*
  * degenerate case of jsonb_build_array where it gets 0 arguments.
  */
@@ -1506,6 +1583,8 @@ clone_parse_state(JsonbParseState *state)
        {
                ocursor->contVal = icursor->contVal;
                ocursor->size = icursor->size;
+               ocursor->unique_keys = icursor->unique_keys;
+               ocursor->skip_nulls = icursor->skip_nulls;
                icursor = icursor->next;
                if (icursor == NULL)
                        break;
@@ -1517,12 +1596,8 @@ clone_parse_state(JsonbParseState *state)
        return result;
 }
 
-
-/*
- * jsonb_agg aggregate function
- */
-Datum
-jsonb_agg_transfn(PG_FUNCTION_ARGS)
+static Datum
+jsonb_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null)
 {
        MemoryContext oldcontext,
                                aggcontext;
@@ -1570,6 +1645,9 @@ jsonb_agg_transfn(PG_FUNCTION_ARGS)
                result = state->res;
        }
 
+       if (absent_on_null && PG_ARGISNULL(1))
+               PG_RETURN_POINTER(state);
+
        /* turn the argument into jsonb in the normal function context */
 
        val = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);
@@ -1639,6 +1717,24 @@ jsonb_agg_transfn(PG_FUNCTION_ARGS)
        PG_RETURN_POINTER(state);
 }
 
+/*
+ * jsonb_agg aggregate function
+ */
+Datum
+jsonb_agg_transfn(PG_FUNCTION_ARGS)
+{
+       return jsonb_agg_transfn_worker(fcinfo, false);
+}
+
+/*
+ * jsonb_agg_strict aggregate function
+ */
+Datum
+jsonb_agg_strict_transfn(PG_FUNCTION_ARGS)
+{
+       return jsonb_agg_transfn_worker(fcinfo, true);
+}
+
 Datum
 jsonb_agg_finalfn(PG_FUNCTION_ARGS)
 {
@@ -1672,11 +1768,9 @@ jsonb_agg_finalfn(PG_FUNCTION_ARGS)
        PG_RETURN_POINTER(out);
 }
 
-/*
- * jsonb_object_agg aggregate function
- */
-Datum
-jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
+static Datum
+jsonb_object_agg_transfn_worker(FunctionCallInfo fcinfo,
+                                                               bool absent_on_null, bool unique_keys)
 {
        MemoryContext oldcontext,
                                aggcontext;
@@ -1690,6 +1784,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
                           *jbval;
        JsonbValue      v;
        JsonbIteratorToken type;
+       bool            skip;
 
        if (!AggCheckCallContext(fcinfo, &aggcontext))
        {
@@ -1709,6 +1804,9 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
                state->res = result;
                result->res = pushJsonbValue(&result->parseState,
                                                                         WJB_BEGIN_OBJECT, NULL);
+               result->parseState->unique_keys = unique_keys;
+               result->parseState->skip_nulls = absent_on_null;
+
                MemoryContextSwitchTo(oldcontext);
 
                arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
@@ -1744,6 +1842,15 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                 errmsg("field name must not be null")));
 
+       /*
+        * Skip null values if absent_on_null unless key uniqueness check is
+        * needed (because we must save keys in this case).
+        */
+       skip = absent_on_null && PG_ARGISNULL(2);
+
+       if (skip && !unique_keys)
+               PG_RETURN_POINTER(state);
+
        val = PG_GETARG_DATUM(1);
 
        memset(&elem, 0, sizeof(JsonbInState));
@@ -1799,6 +1906,16 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
                                }
                                result->res = pushJsonbValue(&result->parseState,
                                                                                         WJB_KEY, &v);
+
+                               if (skip)
+                               {
+                                       v.type = jbvNull;
+                                       result->res = pushJsonbValue(&result->parseState,
+                                                                                                WJB_VALUE, &v);
+                                       MemoryContextSwitchTo(oldcontext);
+                                       PG_RETURN_POINTER(state);
+                               }
+
                                break;
                        case WJB_END_ARRAY:
                                break;
@@ -1871,6 +1988,43 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
        PG_RETURN_POINTER(state);
 }
 
+/*
+ * jsonb_object_agg aggregate function
+ */
+Datum
+jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
+{
+       return jsonb_object_agg_transfn_worker(fcinfo, false, false);
+}
+
+
+/*
+ * jsonb_object_agg_strict aggregate function
+ */
+Datum
+jsonb_object_agg_strict_transfn(PG_FUNCTION_ARGS)
+{
+       return jsonb_object_agg_transfn_worker(fcinfo, true, false);
+}
+
+/*
+ * jsonb_object_agg_unique aggregate function
+ */
+Datum
+jsonb_object_agg_unique_transfn(PG_FUNCTION_ARGS)
+{
+       return jsonb_object_agg_transfn_worker(fcinfo, false, true);
+}
+
+/*
+ * jsonb_object_agg_unique_strict aggregate function
+ */
+Datum
+jsonb_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS)
+{
+       return jsonb_object_agg_transfn_worker(fcinfo, true, true);
+}
+
 Datum
 jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
 {
index e5b1ebf0c368561f796abae8a50560c19a4c72ef..eefa429b9c35cc4b1bcd638e0c3871c110d3b870 100644 (file)
@@ -64,7 +64,8 @@ static int    lengthCompareJsonbStringValue(const void *a, const void *b);
 static int     lengthCompareJsonbString(const char *val1, int len1,
                                                                         const char *val2, int len2);
 static int     lengthCompareJsonbPair(const void *a, const void *b, void *binequal);
-static void uniqueifyJsonbObject(JsonbValue *object);
+static void uniqueifyJsonbObject(JsonbValue *object, bool unique_keys,
+                                                                bool skip_nulls);
 static JsonbValue *pushJsonbValueScalar(JsonbParseState **pstate,
                                                                                JsonbIteratorToken seq,
                                                                                JsonbValue *scalarVal);
@@ -689,7 +690,9 @@ pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq,
                        appendElement(*pstate, scalarVal);
                        break;
                case WJB_END_OBJECT:
-                       uniqueifyJsonbObject(&(*pstate)->contVal);
+                       uniqueifyJsonbObject(&(*pstate)->contVal,
+                                                                (*pstate)->unique_keys,
+                                                                (*pstate)->skip_nulls);
                        /* fall through! */
                case WJB_END_ARRAY:
                        /* Steps here common to WJB_END_OBJECT case */
@@ -732,6 +735,9 @@ pushState(JsonbParseState **pstate)
        JsonbParseState *ns = palloc(sizeof(JsonbParseState));
 
        ns->next = *pstate;
+       ns->unique_keys = false;
+       ns->skip_nulls = false;
+
        return ns;
 }
 
@@ -1936,7 +1942,7 @@ lengthCompareJsonbPair(const void *a, const void *b, void *binequal)
  * Sort and unique-ify pairs in JsonbValue object
  */
 static void
-uniqueifyJsonbObject(JsonbValue *object)
+uniqueifyJsonbObject(JsonbValue *object, bool unique_keys, bool skip_nulls)
 {
        bool            hasNonUniq = false;
 
@@ -1946,23 +1952,43 @@ uniqueifyJsonbObject(JsonbValue *object)
                qsort_arg(object->val.object.pairs, object->val.object.nPairs, sizeof(JsonbPair),
                                  lengthCompareJsonbPair, &hasNonUniq);
 
-       if (hasNonUniq)
+       if (hasNonUniq && unique_keys)
+               ereport(ERROR,
+                               errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE),
+                               errmsg("duplicate JSON object key"));
+
+       if (hasNonUniq || skip_nulls)
        {
-               JsonbPair  *ptr = object->val.object.pairs + 1,
-                                  *res = object->val.object.pairs;
+               JsonbPair  *ptr,
+                                  *res;
+
+               while (skip_nulls && object->val.object.nPairs > 0 &&
+                          object->val.object.pairs->value.type == jbvNull)
+               {
+                       /* If skip_nulls is true, remove leading items with null */
+                       object->val.object.pairs++;
+                       object->val.object.nPairs--;
+               }
 
-               while (ptr - object->val.object.pairs < object->val.object.nPairs)
+               if (object->val.object.nPairs > 0)
                {
-                       /* Avoid copying over duplicate */
-                       if (lengthCompareJsonbStringValue(ptr, res) != 0)
+                       ptr = object->val.object.pairs + 1;
+                       res = object->val.object.pairs;
+
+                       while (ptr - object->val.object.pairs < object->val.object.nPairs)
                        {
-                               res++;
-                               if (ptr != res)
-                                       memcpy(res, ptr, sizeof(JsonbPair));
+                               /* Avoid copying over duplicate or null */
+                               if (lengthCompareJsonbStringValue(ptr, res) != 0 &&
+                                       (!skip_nulls || ptr->value.type != jbvNull))
+                               {
+                                       res++;
+                                       if (ptr != res)
+                                               memcpy(res, ptr, sizeof(JsonbPair));
+                               }
+                               ptr++;
                        }
-                       ptr++;
-               }
 
-               object->val.object.nPairs = res + 1 - object->val.object.pairs;
+                       object->val.object.nPairs = res + 1 - object->val.object.pairs;
+               }
        }
 }
index 4a98b82f07c2cbe4356d3233da7dea340953ee7b..5f953338f3d695df12f5f69d72b96b0f3c51f575 100644 (file)
@@ -457,9 +457,15 @@ static void get_func_expr(FuncExpr *expr, deparse_context *context,
                                                  bool showimplicit);
 static void get_agg_expr(Aggref *aggref, deparse_context *context,
                                                 Aggref *original_aggref);
+static void get_agg_expr_helper(Aggref *aggref, deparse_context *context,
+                                                               Aggref *original_aggref, const char *funcname,
+                                                               const char *options, bool is_json_objectagg);
 static void get_agg_combine_expr(Node *node, deparse_context *context,
                                                                 void *callback_arg);
 static void get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context);
+static void get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
+                                                                          const char *funcname, const char *options,
+                                                                          bool is_json_objectagg);
 static bool get_func_sql_syntax(FuncExpr *expr, deparse_context *context);
 static void get_coercion_expr(Node *arg, deparse_context *context,
                                                          Oid resulttype, int32 resulttypmod,
@@ -467,6 +473,15 @@ static void get_coercion_expr(Node *arg, deparse_context *context,
 static void get_const_expr(Const *constval, deparse_context *context,
                                                   int showtype);
 static void get_const_collation(Const *constval, deparse_context *context);
+static void get_json_format(JsonFormat *format, StringInfo buf);
+static void get_json_constructor(JsonConstructorExpr *ctor,
+                                                                deparse_context *context, bool showimplicit);
+static void get_json_constructor_options(JsonConstructorExpr *ctor,
+                                                                                StringInfo buf);
+static void get_json_agg_constructor(JsonConstructorExpr *ctor,
+                                                                        deparse_context *context,
+                                                                        const char *funcname,
+                                                                        bool is_json_objectagg);
 static void simple_quote_literal(StringInfo buf, const char *val);
 static void get_sublink_expr(SubLink *sublink, deparse_context *context);
 static void get_tablefunc(TableFunc *tf, deparse_context *context,
@@ -6280,7 +6295,8 @@ get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno,
                bool            need_paren = (PRETTY_PAREN(context)
                                                                  || IsA(expr, FuncExpr)
                                                                  || IsA(expr, Aggref)
-                                                                 || IsA(expr, WindowFunc));
+                                                                 || IsA(expr, WindowFunc)
+                                                                 || IsA(expr, JsonConstructorExpr));
 
                if (need_paren)
                        appendStringInfoChar(context->buf, '(');
@@ -8117,6 +8133,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
                case T_GroupingFunc:
                case T_WindowFunc:
                case T_FuncExpr:
+               case T_JsonConstructorExpr:
                        /* function-like: name(..) or name[..] */
                        return true;
 
@@ -8292,6 +8309,11 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
                                        return false;
                        }
 
+               case T_JsonValueExpr:
+                       /* maybe simple, check args */
+                       return isSimpleNode((Node *) ((JsonValueExpr *) node)->raw_expr,
+                                                               node, prettyFlags);
+
                default:
                        break;
        }
@@ -9495,6 +9517,19 @@ get_rule_expr(Node *node, deparse_context *context,
                        }
                        break;
 
+               case T_JsonValueExpr:
+                       {
+                               JsonValueExpr *jve = (JsonValueExpr *) node;
+
+                               get_rule_expr((Node *) jve->raw_expr, context, false);
+                               get_json_format(jve->format, context->buf);
+                       }
+                       break;
+
+               case T_JsonConstructorExpr:
+                       get_json_constructor((JsonConstructorExpr *) node, context, false);
+                       break;
+
                case T_List:
                        {
                                char       *sep;
@@ -9768,11 +9803,24 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
 static void
 get_agg_expr(Aggref *aggref, deparse_context *context,
                         Aggref *original_aggref)
+{
+       get_agg_expr_helper(aggref, context, original_aggref, NULL, NULL,
+                                               false);
+}
+
+/*
+ * get_agg_expr_helper         - subroutine for get_agg_expr and
+ *                                                     get_json_agg_constructor
+ */
+static void
+get_agg_expr_helper(Aggref *aggref, deparse_context *context,
+                                       Aggref *original_aggref, const char *funcname,
+                                       const char *options, bool is_json_objectagg)
 {
        StringInfo      buf = context->buf;
        Oid                     argtypes[FUNC_MAX_ARGS];
        int                     nargs;
-       bool            use_variadic;
+       bool            use_variadic = false;
 
        /*
         * For a combining aggregate, we look up and deparse the corresponding
@@ -9802,13 +9850,14 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
        /* Extract the argument types as seen by the parser */
        nargs = get_aggregate_argtypes(aggref, argtypes);
 
+       if (!funcname)
+               funcname = generate_function_name(aggref->aggfnoid, nargs, NIL,
+                                                                                 argtypes, aggref->aggvariadic,
+                                                                                 &use_variadic,
+                                                                                 context->special_exprkind);
+
        /* Print the aggregate name, schema-qualified if needed */
-       appendStringInfo(buf, "%s(%s",
-                                        generate_function_name(aggref->aggfnoid, nargs,
-                                                                                       NIL, argtypes,
-                                                                                       aggref->aggvariadic,
-                                                                                       &use_variadic,
-                                                                                       context->special_exprkind),
+       appendStringInfo(buf, "%s(%s", funcname,
                                         (aggref->aggdistinct != NIL) ? "DISTINCT " : "");
 
        if (AGGKIND_IS_ORDERED_SET(aggref->aggkind))
@@ -9844,7 +9893,21 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
                                if (tle->resjunk)
                                        continue;
                                if (i++ > 0)
-                                       appendStringInfoString(buf, ", ");
+                               {
+                                       if (is_json_objectagg)
+                                       {
+                                               /*
+                                                * the ABSENT ON NULL and WITH UNIQUE args are printed
+                                                * separately, so ignore them here
+                                                */
+                                               if (i > 2)
+                                                       break;
+
+                                               appendStringInfoString(buf, " : ");
+                                       }
+                                       else
+                                               appendStringInfoString(buf, ", ");
+                               }
                                if (use_variadic && i == nargs)
                                        appendStringInfoString(buf, "VARIADIC ");
                                get_rule_expr(arg, context, true);
@@ -9858,6 +9921,9 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
                }
        }
 
+       if (options)
+               appendStringInfoString(buf, options);
+
        if (aggref->aggfilter != NULL)
        {
                appendStringInfoString(buf, ") FILTER (WHERE ");
@@ -9890,6 +9956,19 @@ get_agg_combine_expr(Node *node, deparse_context *context, void *callback_arg)
  */
 static void
 get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
+{
+       get_windowfunc_expr_helper(wfunc, context, NULL, NULL, false);
+}
+
+
+/*
+ * get_windowfunc_expr_helper  - subroutine for get_windowfunc_expr and
+ *                                                             get_json_agg_constructor
+ */
+static void
+get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
+                                                  const char *funcname, const char *options,
+                                                  bool is_json_objectagg)
 {
        StringInfo      buf = context->buf;
        Oid                     argtypes[FUNC_MAX_ARGS];
@@ -9913,16 +9992,30 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
                nargs++;
        }
 
-       appendStringInfo(buf, "%s(",
-                                        generate_function_name(wfunc->winfnoid, nargs,
-                                                                                       argnames, argtypes,
-                                                                                       false, NULL,
-                                                                                       context->special_exprkind));
+       if (!funcname)
+               funcname = generate_function_name(wfunc->winfnoid, nargs, argnames,
+                                                                                 argtypes, false, NULL,
+                                                                                 context->special_exprkind);
+
+       appendStringInfo(buf, "%s(", funcname);
+
        /* winstar can be set only in zero-argument aggregates */
        if (wfunc->winstar)
                appendStringInfoChar(buf, '*');
        else
-               get_rule_expr((Node *) wfunc->args, context, true);
+       {
+               if (is_json_objectagg)
+               {
+                       get_rule_expr((Node *) linitial(wfunc->args), context, false);
+                       appendStringInfoString(buf, " : ");
+                       get_rule_expr((Node *) lsecond(wfunc->args), context, false);
+               }
+               else
+                       get_rule_expr((Node *) wfunc->args, context, true);
+       }
+
+       if (options)
+               appendStringInfoString(buf, options);
 
        if (wfunc->aggfilter != NULL)
        {
@@ -10483,6 +10576,158 @@ get_const_collation(Const *constval, deparse_context *context)
        }
 }
 
+/*
+ * get_json_format                     - Parse back a JsonFormat node
+ */
+static void
+get_json_format(JsonFormat *format, StringInfo buf)
+{
+       if (format->format_type == JS_FORMAT_DEFAULT)
+               return;
+
+       appendStringInfoString(buf,
+                                                  format->format_type == JS_FORMAT_JSONB ?
+                                                  " FORMAT JSONB" : " FORMAT JSON");
+
+       if (format->encoding != JS_ENC_DEFAULT)
+       {
+               const char *encoding;
+
+               encoding =
+                       format->encoding == JS_ENC_UTF16 ? "UTF16" :
+                       format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8";
+
+               appendStringInfo(buf, " ENCODING %s", encoding);
+       }
+}
+
+/*
+ * get_json_returning          - Parse back a JsonReturning structure
+ */
+static void
+get_json_returning(JsonReturning *returning, StringInfo buf,
+                                  bool json_format_by_default)
+{
+       if (!OidIsValid(returning->typid))
+               return;
+
+       appendStringInfo(buf, " RETURNING %s",
+                                        format_type_with_typemod(returning->typid,
+                                                                                         returning->typmod));
+
+       if (!json_format_by_default ||
+               returning->format->format_type !=
+               (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON))
+               get_json_format(returning->format, buf);
+}
+
+/*
+ * get_json_constructor                - Parse back a JsonConstructorExpr node
+ */
+static void
+get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context,
+                                        bool showimplicit)
+{
+       StringInfo      buf = context->buf;
+       const char *funcname;
+       bool            is_json_object;
+       int                     curridx;
+       ListCell   *lc;
+
+       if (ctor->type == JSCTOR_JSON_OBJECTAGG)
+       {
+               get_json_agg_constructor(ctor, context, "JSON_OBJECTAGG", true);
+               return;
+       }
+       else if (ctor->type == JSCTOR_JSON_ARRAYAGG)
+       {
+               get_json_agg_constructor(ctor, context, "JSON_ARRAYAGG", false);
+               return;
+       }
+
+       switch (ctor->type)
+       {
+               case JSCTOR_JSON_OBJECT:
+                       funcname = "JSON_OBJECT";
+                       break;
+               case JSCTOR_JSON_ARRAY:
+                       funcname = "JSON_ARRAY";
+                       break;
+               default:
+                       elog(ERROR, "invalid JsonConstructorExprType %d", ctor->type);
+       }
+
+       appendStringInfo(buf, "%s(", funcname);
+
+       is_json_object = ctor->type == JSCTOR_JSON_OBJECT;
+       foreach(lc, ctor->args)
+       {
+               curridx = foreach_current_index(lc);
+               if (curridx > 0)
+               {
+                       const char *sep;
+
+                       sep = (is_json_object && (curridx % 2) != 0) ? " : " : ", ";
+                       appendStringInfoString(buf, sep);
+               }
+
+               get_rule_expr((Node *) lfirst(lc), context, true);
+       }
+
+       get_json_constructor_options(ctor, buf);
+       appendStringInfo(buf, ")");
+}
+
+/*
+ * Append options, if any, to the JSON constructor being deparsed
+ */
+static void
+get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf)
+{
+       if (ctor->absent_on_null)
+       {
+               if (ctor->type == JSCTOR_JSON_OBJECT ||
+                       ctor->type == JSCTOR_JSON_OBJECTAGG)
+                       appendStringInfoString(buf, " ABSENT ON NULL");
+       }
+       else
+       {
+               if (ctor->type == JSCTOR_JSON_ARRAY ||
+                       ctor->type == JSCTOR_JSON_ARRAYAGG)
+                       appendStringInfoString(buf, " NULL ON NULL");
+       }
+
+       if (ctor->unique)
+               appendStringInfoString(buf, " WITH UNIQUE KEYS");
+
+       get_json_returning(ctor->returning, buf, true);
+}
+
+/*
+ * get_json_agg_constructor - Parse back an aggregate JsonConstructorExpr node
+ */
+static void
+get_json_agg_constructor(JsonConstructorExpr *ctor, deparse_context *context,
+                                                const char *funcname, bool is_json_objectagg)
+{
+       StringInfoData options;
+
+       initStringInfo(&options);
+       get_json_constructor_options(ctor, &options);
+
+       if (IsA(ctor->func, Aggref))
+               get_agg_expr_helper((Aggref *) ctor->func, context,
+                                                       (Aggref *) ctor->func,
+                                                       funcname, options.data, is_json_objectagg);
+       else if (IsA(ctor->func, WindowFunc))
+               get_windowfunc_expr_helper((WindowFunc *) ctor->func, context,
+                                                                  funcname, options.data,
+                                                                  is_json_objectagg);
+       else
+               elog(ERROR, "invalid JsonConstructorExpr underlying node type: %d",
+                        nodeTag(ctor->func));
+}
+
 /*
  * simple_quote_literal - Format a string as a SQL literal, append to buf
  */
index 229b0bd54eaea395022fc8c1f72e12f555d25f33..f0989417f058da27e2d12f1772ccfdf7f1203c9a 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202303292
+#define CATALOG_VERSION_NO     202303293
 
 #endif
index d7895cd676b736cc231cdb97df43126b3c4c3f26..283f494bf598ab5d49d0fea37d24237f0ca1d1c6 100644 (file)
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'json_agg_strict', aggtransfn => 'json_agg_strict_transfn',
+  aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
 { aggfnoid => 'json_object_agg', aggtransfn => 'json_object_agg_transfn',
   aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'json_object_agg_unique',
+  aggtransfn => 'json_object_agg_unique_transfn',
+  aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'json_object_agg_strict',
+  aggtransfn => 'json_object_agg_strict_transfn',
+  aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'json_object_agg_unique_strict',
+  aggtransfn => 'json_object_agg_unique_strict_transfn',
+  aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' },
 
 # jsonb
 { aggfnoid => 'jsonb_agg', aggtransfn => 'jsonb_agg_transfn',
   aggfinalfn => 'jsonb_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'jsonb_agg_strict', aggtransfn => 'jsonb_agg_strict_transfn',
+  aggfinalfn => 'jsonb_agg_finalfn', aggtranstype => 'internal' },
 { aggfnoid => 'jsonb_object_agg', aggtransfn => 'jsonb_object_agg_transfn',
   aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'jsonb_object_agg_unique',
+  aggtransfn => 'jsonb_object_agg_unique_transfn',
+  aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'jsonb_object_agg_strict',
+  aggtransfn => 'jsonb_object_agg_strict_transfn',
+  aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'jsonb_object_agg_unique_strict',
+  aggtransfn => 'jsonb_object_agg_unique_strict_transfn',
+  aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' },
 
 # ordered-set and hypothetical-set aggregates
 { aggfnoid => 'percentile_disc(float8,anyelement)', aggkind => 'o',
index def8ee2045536ce3e419bbfceb087d4928f17d4b..5736c1082cfe246d5fe7478877a1eed5240dbd31 100644 (file)
   proname => 'json_agg_transfn', proisstrict => 'f', provolatile => 's',
   prorettype => 'internal', proargtypes => 'internal anyelement',
   prosrc => 'json_agg_transfn' },
+{ oid => '8950', descr => 'json aggregate transition function',
+  proname => 'json_agg_strict_transfn', proisstrict => 'f', provolatile => 's',
+  prorettype => 'internal', proargtypes => 'internal anyelement',
+  prosrc => 'json_agg_strict_transfn' },
 { oid => '3174', descr => 'json aggregate final function',
   proname => 'json_agg_finalfn', proisstrict => 'f', prorettype => 'json',
   proargtypes => 'internal', prosrc => 'json_agg_finalfn' },
   proname => 'json_agg', prokind => 'a', proisstrict => 'f', provolatile => 's',
   prorettype => 'json', proargtypes => 'anyelement',
   prosrc => 'aggregate_dummy' },
+{ oid => '8951', descr => 'aggregate input into json',
+  proname => 'json_agg_strict', prokind => 'a', proisstrict => 'f',
+  provolatile => 's', prorettype => 'json', proargtypes => 'anyelement',
+  prosrc => 'aggregate_dummy' },
 { oid => '3180', descr => 'json object aggregate transition function',
   proname => 'json_object_agg_transfn', proisstrict => 'f', provolatile => 's',
   prorettype => 'internal', proargtypes => 'internal any any',
   prosrc => 'json_object_agg_transfn' },
+{ oid => '8952', descr => 'json object aggregate transition function',
+  proname => 'json_object_agg_strict_transfn', proisstrict => 'f',
+  provolatile => 's', prorettype => 'internal',
+  proargtypes => 'internal any any',
+  prosrc => 'json_object_agg_strict_transfn' },
+{ oid => '8953', descr => 'json object aggregate transition function',
+  proname => 'json_object_agg_unique_transfn', proisstrict => 'f',
+  provolatile => 's', prorettype => 'internal',
+  proargtypes => 'internal any any',
+  prosrc => 'json_object_agg_unique_transfn' },
+{ oid => '8954', descr => 'json object aggregate transition function',
+  proname => 'json_object_agg_unique_strict_transfn', proisstrict => 'f',
+  provolatile => 's', prorettype => 'internal',
+  proargtypes => 'internal any any',
+  prosrc => 'json_object_agg_unique_strict_transfn' },
 { oid => '3196', descr => 'json object aggregate final function',
   proname => 'json_object_agg_finalfn', proisstrict => 'f',
   prorettype => 'json', proargtypes => 'internal',
   proname => 'json_object_agg', prokind => 'a', proisstrict => 'f',
   provolatile => 's', prorettype => 'json', proargtypes => 'any any',
   prosrc => 'aggregate_dummy' },
+{ oid => '8955', descr => 'aggregate non-NULL input into a json object',
+  proname => 'json_object_agg_strict', prokind => 'a', proisstrict => 'f',
+  provolatile => 's', prorettype => 'json', proargtypes => 'any any',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8956',
+  descr => 'aggregate input into a json object with unique keys',
+  proname => 'json_object_agg_unique', prokind => 'a', proisstrict => 'f',
+  provolatile => 's', prorettype => 'json', proargtypes => 'any any',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8957',
+  descr => 'aggregate non-NULL input into a json object with unique keys',
+  proname => 'json_object_agg_unique_strict', prokind => 'a',
+  proisstrict => 'f', provolatile => 's', prorettype => 'json',
+  proargtypes => 'any any', prosrc => 'aggregate_dummy' },
 { oid => '3198', descr => 'build a json array from any inputs',
   proname => 'json_build_array', provariadic => 'any', proisstrict => 'f',
   provolatile => 's', prorettype => 'json', proargtypes => 'any',
   proname => 'jsonb_agg_transfn', proisstrict => 'f', provolatile => 's',
   prorettype => 'internal', proargtypes => 'internal anyelement',
   prosrc => 'jsonb_agg_transfn' },
+{ oid => '8958', descr => 'jsonb aggregate transition function',
+  proname => 'jsonb_agg_strict_transfn', proisstrict => 'f', provolatile => 's',
+  prorettype => 'internal', proargtypes => 'internal anyelement',
+  prosrc => 'jsonb_agg_strict_transfn' },
 { oid => '3266', descr => 'jsonb aggregate final function',
   proname => 'jsonb_agg_finalfn', proisstrict => 'f', provolatile => 's',
   prorettype => 'jsonb', proargtypes => 'internal',
   proname => 'jsonb_agg', prokind => 'a', proisstrict => 'f',
   provolatile => 's', prorettype => 'jsonb', proargtypes => 'anyelement',
   prosrc => 'aggregate_dummy' },
+{ oid => '8959', descr => 'aggregate input into jsonb skipping nulls',
+  proname => 'jsonb_agg_strict', prokind => 'a', proisstrict => 'f',
+  provolatile => 's', prorettype => 'jsonb', proargtypes => 'anyelement',
+  prosrc => 'aggregate_dummy' },
 { oid => '3268', descr => 'jsonb object aggregate transition function',
   proname => 'jsonb_object_agg_transfn', proisstrict => 'f', provolatile => 's',
   prorettype => 'internal', proargtypes => 'internal any any',
   prosrc => 'jsonb_object_agg_transfn' },
+{ oid => '8960', descr => 'jsonb object aggregate transition function',
+  proname => 'jsonb_object_agg_strict_transfn', proisstrict => 'f',
+  provolatile => 's', prorettype => 'internal',
+  proargtypes => 'internal any any',
+  prosrc => 'jsonb_object_agg_strict_transfn' },
+{ oid => '8961', descr => 'jsonb object aggregate transition function',
+  proname => 'jsonb_object_agg_unique_transfn', proisstrict => 'f',
+  provolatile => 's', prorettype => 'internal',
+  proargtypes => 'internal any any',
+  prosrc => 'jsonb_object_agg_unique_transfn' },
+{ oid => '8962', descr => 'jsonb object aggregate transition function',
+  proname => 'jsonb_object_agg_unique_strict_transfn', proisstrict => 'f',
+  provolatile => 's', prorettype => 'internal',
+  proargtypes => 'internal any any',
+  prosrc => 'jsonb_object_agg_unique_strict_transfn' },
 { oid => '3269', descr => 'jsonb object aggregate final function',
   proname => 'jsonb_object_agg_finalfn', proisstrict => 'f', provolatile => 's',
   prorettype => 'jsonb', proargtypes => 'internal',
   proname => 'jsonb_object_agg', prokind => 'a', proisstrict => 'f',
   prorettype => 'jsonb', proargtypes => 'any any',
   prosrc => 'aggregate_dummy' },
+{ oid => '8963', descr => 'aggregate non-NULL inputs into jsonb object',
+  proname => 'jsonb_object_agg_strict', prokind => 'a', proisstrict => 'f',
+  prorettype => 'jsonb', proargtypes => 'any any',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8964',
+  descr => 'aggregate inputs into jsonb object checking key uniqueness',
+  proname => 'jsonb_object_agg_unique', prokind => 'a', proisstrict => 'f',
+  prorettype => 'jsonb', proargtypes => 'any any',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8965',
+  descr => 'aggregate non-NULL inputs into jsonb object checking key uniqueness',
+  proname => 'jsonb_object_agg_unique_strict', prokind => 'a',
+  proisstrict => 'f', prorettype => 'jsonb', proargtypes => 'any any',
+  prosrc => 'aggregate_dummy' },
 { oid => '3271', descr => 'build a jsonb array from any inputs',
   proname => 'jsonb_build_array', provariadic => 'any', proisstrict => 'f',
   provolatile => 's', prorettype => 'jsonb', proargtypes => 'any',
index 06c3adc0a194ed0e2564a5ffe52f1ec42dbb7e68..f5a72a8b40f0db4259021407ef8353d6ef641ee0 100644 (file)
@@ -21,6 +21,7 @@
 struct ExprEvalStep;
 struct SubscriptingRefState;
 struct ScalarArrayOpExprHashTable;
+struct JsonConstructorExprState;
 
 /* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
 /* expression's interpreter has been initialized */
@@ -234,6 +235,7 @@ typedef enum ExprEvalOp
        EEOP_SCALARARRAYOP,
        EEOP_HASHED_SCALARARRAYOP,
        EEOP_XMLEXPR,
+       EEOP_JSON_CONSTRUCTOR,
        EEOP_AGGREF,
        EEOP_GROUPING_FUNC,
        EEOP_WINDOW_FUNC,
@@ -588,6 +590,12 @@ typedef struct ExprEvalStep
                        bool       *argnull;
                }                       xmlexpr;
 
+               /* for EEOP_JSON_CONSTRUCTOR */
+               struct
+               {
+                       struct JsonConstructorExprState *jcstate;
+               }                       json_constructor;
+
                /* for EEOP_AGGREF */
                struct
                {
@@ -666,6 +674,7 @@ typedef struct ExprEvalStep
                        int                     transno;
                        int                     setoff;
                }                       agg_trans;
+
        }                       d;
 } ExprEvalStep;
 
@@ -714,6 +723,21 @@ typedef struct SubscriptExecSteps
        ExecEvalSubroutine sbs_fetch_old;       /* fetch old value for assignment */
 } SubscriptExecSteps;
 
+/* EEOP_JSON_CONSTRUCTOR state, too big to inline */
+typedef struct JsonConstructorExprState
+{
+       JsonConstructorExpr *constructor;
+       Datum      *arg_values;
+       bool       *arg_nulls;
+       Oid                *arg_types;
+       struct
+       {
+               int                     category;
+               Oid                     outfuncid;
+       }                  *arg_type_cache; /* cache for datum_to_json[b]() */
+       int                     nargs;
+} JsonConstructorExprState;
+
 
 /* functions in execExpr.c */
 extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
@@ -770,6 +794,8 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
                                                                ExprContext *econtext);
 extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
                                                   ExprContext *econtext, TupleTableSlot *slot);
+extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
+                                                                       ExprContext *econtext);
 
 extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
                                                         ExprContext *aggcontext);
index 64651c9b00b3fc35670beb9b7c26f5cf6e8d46f5..50aa00e0c1b4dc81c1845948b9671e04613c05f0 100644 (file)
@@ -108,4 +108,10 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
 
+extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding,
+                                                                 int location);
+extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format);
+extern Node *makeJsonKeyValue(Node *key, Node *value);
+extern JsonEncoding makeJsonEncoding(char *name);
+
 #endif                                                 /* MAKEFUNC_H */
index 028588fb3300d266e74bf1a00707f3a5e6aed884..1c296da326f5e72763c50f65293b2a2cedc3f2fb 100644 (file)
@@ -1713,6 +1713,113 @@ typedef struct TriggerTransition
        bool            isTable;
 } TriggerTransition;
 
+/* Nodes for SQL/JSON support */
+
+/*
+ * JsonOutput -
+ *             representation of JSON output clause (RETURNING type [FORMAT format])
+ */
+typedef struct JsonOutput
+{
+       NodeTag         type;
+       TypeName   *typeName;           /* RETURNING type name, if specified */
+       JsonReturning *returning;       /* RETURNING FORMAT clause and type Oids */
+} JsonOutput;
+
+/*
+ * JsonKeyValue -
+ *             untransformed representation of JSON object key-value pair for
+ *             JSON_OBJECT() and JSON_OBJECTAGG()
+ */
+typedef struct JsonKeyValue
+{
+       NodeTag         type;
+       Expr       *key;                        /* key expression */
+       JsonValueExpr *value;           /* JSON value expression */
+} JsonKeyValue;
+
+/*
+ * JsonObjectConstructor -
+ *             untransformed representation of JSON_OBJECT() constructor
+ */
+typedef struct JsonObjectConstructor
+{
+       NodeTag         type;
+       List       *exprs;                      /* list of JsonKeyValue pairs */
+       JsonOutput *output;                     /* RETURNING clause, if specified  */
+       bool            absent_on_null; /* skip NULL values? */
+       bool            unique;                 /* check key uniqueness? */
+       int                     location;               /* token location, or -1 if unknown */
+} JsonObjectConstructor;
+
+/*
+ * JsonArrayConstructor -
+ *             untransformed representation of JSON_ARRAY(element,...) constructor
+ */
+typedef struct JsonArrayConstructor
+{
+       NodeTag         type;
+       List       *exprs;                      /* list of JsonValueExpr elements */
+       JsonOutput *output;                     /* RETURNING clause, if specified  */
+       bool            absent_on_null; /* skip NULL elements? */
+       int                     location;               /* token location, or -1 if unknown */
+} JsonArrayConstructor;
+
+/*
+ * JsonArrayQueryConstructor -
+ *             untransformed representation of JSON_ARRAY(subquery) constructor
+ */
+typedef struct JsonArrayQueryConstructor
+{
+       NodeTag         type;
+       Node       *query;                      /* subquery */
+       JsonOutput *output;                     /* RETURNING clause, if specified  */
+       JsonFormat *format;                     /* FORMAT clause for subquery, if specified */
+       bool            absent_on_null; /* skip NULL elements? */
+       int                     location;               /* token location, or -1 if unknown */
+} JsonArrayQueryConstructor;
+
+/*
+ * JsonAggConstructor -
+ *             common fields of untransformed representation of
+ *             JSON_ARRAYAGG() and JSON_OBJECTAGG()
+ */
+typedef struct JsonAggConstructor
+{
+       NodeTag         type;
+       JsonOutput *output;                     /* RETURNING clause, if any */
+       Node       *agg_filter;         /* FILTER clause, if any */
+       List       *agg_order;          /* ORDER BY clause, if any */
+       struct WindowDef *over;         /* OVER clause, if any */
+       int                     location;               /* token location, or -1 if unknown */
+} JsonAggConstructor;
+
+/*
+ * JsonObjectAgg -
+ *             untransformed representation of JSON_OBJECTAGG()
+ */
+typedef struct JsonObjectAgg
+{
+       NodeTag         type;
+       JsonAggConstructor *constructor;        /* common fields */
+       JsonKeyValue *arg;                      /* object key-value pair */
+       bool            absent_on_null; /* skip NULL values? */
+       bool            unique;                 /* check key uniqueness? */
+} JsonObjectAgg;
+
+/*
+ * JsonArrayAgg -
+ *             untransformed representation of JSON_ARRRAYAGG()
+ */
+typedef struct JsonArrayAgg
+{
+       NodeTag         type;
+       JsonAggConstructor *constructor;        /* common fields */
+       JsonValueExpr *arg;                     /* array element expression */
+       bool            absent_on_null; /* skip NULL elements? */
+} JsonArrayAgg;
+
+
 /*****************************************************************************
  *             Raw Grammar Output Statements
  *****************************************************************************/
index 8fb5b4b919b837b2fd39058774ff5b17f1447f21..de1701c21364fca21a545ed75d5c8476d44f8648 100644 (file)
@@ -1498,6 +1498,91 @@ typedef struct XmlExpr
        int                     location;
 } XmlExpr;
 
+/*
+ * JsonEncoding -
+ *             representation of JSON ENCODING clause
+ */
+typedef enum JsonEncoding
+{
+       JS_ENC_DEFAULT,                         /* unspecified */
+       JS_ENC_UTF8,
+       JS_ENC_UTF16,
+       JS_ENC_UTF32,
+} JsonEncoding;
+
+/*
+ * JsonFormatType -
+ *             enumeration of JSON formats used in JSON FORMAT clause
+ */
+typedef enum JsonFormatType
+{
+       JS_FORMAT_DEFAULT,                      /* unspecified */
+       JS_FORMAT_JSON,                         /* FORMAT JSON [ENCODING ...] */
+       JS_FORMAT_JSONB                         /* implicit internal format for RETURNING
+                                                                * jsonb */
+} JsonFormatType;
+
+/*
+ * JsonFormat -
+ *             representation of JSON FORMAT clause
+ */
+typedef struct JsonFormat
+{
+       NodeTag         type;
+       JsonFormatType format_type; /* format type */
+       JsonEncoding encoding;          /* JSON encoding */
+       int                     location;               /* token location, or -1 if unknown */
+} JsonFormat;
+
+/*
+ * JsonReturning -
+ *             transformed representation of JSON RETURNING clause
+ */
+typedef struct JsonReturning
+{
+       NodeTag         type;
+       JsonFormat *format;                     /* output JSON format */
+       Oid                     typid;                  /* target type Oid */
+       int32           typmod;                 /* target type modifier */
+} JsonReturning;
+
+/*
+ * JsonValueExpr -
+ *             representation of JSON value expression (expr [FORMAT json_format])
+ */
+typedef struct JsonValueExpr
+{
+       NodeTag         type;
+       Expr       *raw_expr;           /* raw expression */
+       Expr       *formatted_expr; /* formatted expression or NULL */
+       JsonFormat *format;                     /* FORMAT clause, if specified */
+} JsonValueExpr;
+
+typedef enum JsonConstructorType
+{
+       JSCTOR_JSON_OBJECT = 1,
+       JSCTOR_JSON_ARRAY = 2,
+       JSCTOR_JSON_OBJECTAGG = 3,
+       JSCTOR_JSON_ARRAYAGG = 4
+} JsonConstructorType;
+
+/*
+ * JsonConstructorExpr -
+ *             wrapper over FuncExpr/Aggref/WindowFunc for SQL/JSON constructors
+ */
+typedef struct JsonConstructorExpr
+{
+       Expr            xpr;
+       JsonConstructorType type;       /* constructor type */
+       List       *args;
+       Expr       *func;                       /* underlying json[b]_xxx() function call */
+       Expr       *coercion;           /* coercion to RETURNING type */
+       JsonReturning *returning;       /* RETURNING clause */
+       bool            absent_on_null; /* ABSENT ON NULL? */
+       bool            unique;                 /* WITH UNIQUE KEYS? (JSON_OBJECT[AGG] only) */
+       int                     location;
+} JsonConstructorExpr;
+
 /* ----------------
  * NullTest
  *
index 753e9ee174250a9ea976e1c7b1403aa5e538d7d7..868f389a04ddc3854fab002fcf3f7a054bddfaf5 100644 (file)
@@ -26,6 +26,7 @@
 
 /* name, value, category, is-bare-label */
 PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("absent", ABSENT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -175,6 +176,7 @@ PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("for", FOR, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("foreign", FOREIGN, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("format", FORMAT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("forward", FORWARD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("freeze", FREEZE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("from", FROM, RESERVED_KEYWORD, AS_LABEL)
@@ -228,7 +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_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("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD, BARE_LABEL)
index 23e3cc41d657fe1158a8f82bc5998bde6dc7db11..b75f7d929dd40d896c6b1e8c847259f9d3215069 100644 (file)
 extern void escape_json(StringInfo buf, const char *str);
 extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
                                                                const int *tzp);
+extern bool to_json_is_immutable(Oid typoid);
+extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls,
+                                                                         Oid *types, bool absent_on_null,
+                                                                         bool unique_keys);
+extern Datum json_build_array_worker(int nargs, Datum *args, bool *nulls,
+                                                                        Oid *types, bool absent_on_null);
 
 #endif                                                 /* JSON_H */
index 701e063abd489127bc7aed14d6c6a6b8b6cc3a39..649a1644f24c2cf0f7dd894db5321408fc58f12a 100644 (file)
@@ -321,6 +321,8 @@ typedef struct JsonbParseState
        JsonbValue      contVal;
        Size            size;
        struct JsonbParseState *next;
+       bool            unique_keys;    /* Check object key uniqueness */
+       bool            skip_nulls;             /* Skip null object fields */
 } JsonbParseState;
 
 /*
@@ -427,4 +429,11 @@ extern Datum jsonb_set_element(Jsonb *jb, Datum *path, int path_len,
                                                           JsonbValue *newval);
 extern Datum jsonb_get_element(Jsonb *jb, Datum *path, int npath,
                                                           bool *isnull, bool as_text);
+extern bool to_jsonb_is_immutable(Oid typoid);
+extern Datum jsonb_build_object_worker(int nargs, Datum *args, bool *nulls,
+                                                                          Oid *types, bool absent_on_null,
+                                                                          bool unique_keys);
+extern Datum jsonb_build_array_worker(int nargs, Datum *args, bool *nulls,
+                                                                         Oid *types, bool absent_on_null);
+
 #endif                                                 /* __JSONB_H__ */
index 296cd7193c78d13b56cd862c1f9e8ab23896addb..faeb460ef52f3772e8466042629b2deb744d1180 100644 (file)
@@ -55,9 +55,11 @@ my %replace_token = (
 
 # or in the block
 my %replace_string = (
+       'FORMAT_LA'      => 'format',
        'NOT_LA'         => 'not',
        'NULLS_LA'       => 'nulls',
        'WITH_LA'        => 'with',
+       'WITHOUT_LA'     => 'without',
        'TYPECAST'       => '::',
        'DOT_DOT'        => '..',
        'COLON_EQUALS'   => ':=',
index f447dc5d84b5b51470a2572f367583cb14f1f39f..a40f4bef093c8d1e34e6965989567260ec234150 100644 (file)
@@ -78,9 +78,11 @@ filtered_base_yylex(void)
         */
        switch (cur_token)
        {
+               case FORMAT:
                case NOT:
                case NULLS_P:
                case WITH:
+               case WITHOUT:
                case UIDENT:
                case USCONST:
                        break;
@@ -110,6 +112,16 @@ filtered_base_yylex(void)
        /* Replace cur_token if needed, based on lookahead */
        switch (cur_token)
        {
+               case FORMAT:
+                       /* Replace FORMAT by FORMAT_LA if it's followed by JSON */
+                       switch (next_token)
+                       {
+                               case JSON:
+                                       cur_token = FORMAT_LA;
+                                       break;
+                       }
+                       break;
+
                case NOT:
                        /* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
                        switch (next_token)
@@ -145,6 +157,16 @@ filtered_base_yylex(void)
                                        break;
                        }
                        break;
+
+               case WITHOUT:
+                       /* Replace WITHOUT by WITHOUT_LA if it's followed by UNIQUE */
+                       switch (next_token)
+                       {
+                               case UNIQUE:
+                                       cur_token = WITHOUT_LA;
+                                       break;
+                       }
+                       break;
                case UIDENT:
                case USCONST:
                        /* Look ahead for UESCAPE */
index e034c5a420c3cdd78464146a1739634e99611f22..39814a39c1767b561449088c89e925a4727e4641 100644 (file)
@@ -50,6 +50,7 @@ test: sql/indicators
 test: sql/oldexec
 test: sql/quote
 test: sql/show
+test: sql/sqljson
 test: sql/insupd
 test: sql/parser
 test: sql/prepareas
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
new file mode 100644 (file)
index 0000000..6478454
--- /dev/null
@@ -0,0 +1,203 @@
+/* Processed by ecpg (regression mode) */
+/* These include files are added by the preprocessor */
+#include <ecpglib.h>
+#include <ecpgerrno.h>
+#include <sqlca.h>
+/* End of automatic include section */
+#define ECPGdebug(X,Y) ECPGdebug((X)+100,(Y))
+
+#line 1 "sqljson.pgc"
+#include <stdio.h>
+
+
+#line 1 "sqlca.h"
+#ifndef POSTGRES_SQLCA_H
+#define POSTGRES_SQLCA_H
+
+#ifndef PGDLLIMPORT
+#if  defined(WIN32) || defined(__CYGWIN__)
+#define PGDLLIMPORT __declspec (dllimport)
+#else
+#define PGDLLIMPORT
+#endif                                                 /* __CYGWIN__ */
+#endif                                                 /* PGDLLIMPORT */
+
+#define SQLERRMC_LEN   150
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+struct sqlca_t
+{
+       char            sqlcaid[8];
+       long            sqlabc;
+       long            sqlcode;
+       struct
+       {
+               int                     sqlerrml;
+               char            sqlerrmc[SQLERRMC_LEN];
+       }                       sqlerrm;
+       char            sqlerrp[8];
+       long            sqlerrd[6];
+       /* Element 0: empty                                             */
+       /* 1: OID of processed tuple if applicable                      */
+       /* 2: number of rows processed                          */
+       /* after an INSERT, UPDATE or                           */
+       /* DELETE statement                                     */
+       /* 3: empty                                             */
+       /* 4: empty                                             */
+       /* 5: empty                                             */
+       char            sqlwarn[8];
+       /* Element 0: set to 'W' if at least one other is 'W'   */
+       /* 1: if 'W' at least one character string              */
+       /* value was truncated when it was                      */
+       /* stored into a host variable.             */
+
+       /*
+        * 2: if 'W' a (hopefully) non-fatal notice occurred
+        */     /* 3: empty */
+       /* 4: empty                                             */
+       /* 5: empty                                             */
+       /* 6: empty                                             */
+       /* 7: empty                                             */
+
+       char            sqlstate[5];
+};
+
+struct sqlca_t *ECPGget_sqlca(void);
+
+#ifndef POSTGRES_ECPG_INTERNAL
+#define sqlca (*ECPGget_sqlca())
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#line 3 "sqljson.pgc"
+
+
+#line 1 "regression.h"
+
+
+
+
+
+
+#line 4 "sqljson.pgc"
+
+
+/* exec sql whenever sqlerror  sqlprint ; */
+#line 6 "sqljson.pgc"
+
+
+int
+main ()
+{
+/* exec sql begin declare section */
+   
+
+#line 12 "sqljson.pgc"
+ char json [ 1024 ] ;
+/* exec sql end declare section */
+#line 13 "sqljson.pgc"
+
+
+  ECPGdebug (1, stderr);
+
+  { ECPGconnect(__LINE__, 0, "ecpg1_regression" , NULL, NULL , NULL, 0); 
+#line 17 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 17 "sqljson.pgc"
+
+  { ECPGsetcommit(__LINE__, "on", NULL);
+#line 18 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 18 "sqljson.pgc"
+
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( returning text )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 20 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 20 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( returning text format json )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 23 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 23 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_array ( returning jsonb )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 26 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 26 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_array ( returning jsonb format json )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 29 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 29 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( 1 : 1 , '1' : null with unique )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 32 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 32 "sqljson.pgc"
+
+  // error
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( 1 : 1 , '2' : null , 1 : '2' absent on null without unique keys )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 35 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 35 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( 1 : 1 , '2' : null absent on null without unique returning jsonb )", ECPGt_EOIT, 
+       ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 38 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 38 "sqljson.pgc"
+
+  printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 41 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 41 "sqljson.pgc"
+
+
+  return 0;
+}
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
new file mode 100644 (file)
index 0000000..907f773
--- /dev/null
@@ -0,0 +1,69 @@
+[NO_PID]: ECPGdebug: set to 1
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ECPGconnect: opening database ecpg1_regression on <DEFAULT> port <DEFAULT>  
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ECPGsetcommit on line 18: action "on"; connection "ecpg1_regression"
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 20: query: select json_object ( returning text ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 20: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 20: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 20: RESULT: {} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 23: query: select json_object ( returning text format json ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 23: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 23: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 23: RESULT: {} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 26: query: select json_array ( returning jsonb ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 26: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 26: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_is_type_an_array on line 26: type (3802); C (1); array (no)
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 26: RESULT: [] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 29: query: select json_array ( returning jsonb format json ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 29: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 29: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 29: RESULT: [] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 32: query: select json_object ( 1 : 1 , '1' : null with unique ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 32: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 32: bad response - ERROR:  duplicate JSON key "1"
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 22030 (sqlcode -400): duplicate JSON key "1" on line 32
+[NO_PID]: sqlca: code: -400, state: 22030
+SQL error: duplicate JSON key "1" on line 32
+[NO_PID]: ecpg_execute on line 35: query: select json_object ( 1 : 1 , '2' : null , 1 : '2' absent on null without unique keys ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 35: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 35: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_is_type_an_array on line 35: type (114); C (1); array (no)
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 35: RESULT: {"1" : 1, "1" : "2"} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 38: query: select json_object ( 1 : 1 , '2' : null absent on null without unique returning jsonb ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 38: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 38: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 38: RESULT: {"1": 1} 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
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
new file mode 100644 (file)
index 0000000..aae052a
--- /dev/null
@@ -0,0 +1,6 @@
+Found json={}
+Found json={}
+Found json=[]
+Found json=[]
+Found json={"1" : 1, "1" : "2"}
+Found json={"1": 1}
index 876ca8df3e644c87bf6d92295a7820ac19be7603..d8213b25ceaef95c2e3d97a0f85db4b9f312c771 100644 (file)
@@ -23,6 +23,7 @@ TESTS = array array.c \
         parser parser.c \
         quote quote.c \
         show show.c \
+        sqljson sqljson.c \
         insupd insupd.c \
         twophase twophase.c \
         insupd insupd.c \
index 5149a738108d80de6062034cb313eae7829e27e1..f4c9418abb88ebf2cdfc52d7fbe3016305e8a608 100644 (file)
@@ -25,6 +25,7 @@ pgc_files = [
   'quote',
   'show',
   'sqlda',
+  'sqljson',
   'twophase',
 ]
 
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
new file mode 100644 (file)
index 0000000..6a582b5
--- /dev/null
@@ -0,0 +1,44 @@
+#include <stdio.h>
+
+EXEC SQL INCLUDE sqlca;
+exec sql include ../regression;
+
+EXEC SQL WHENEVER SQLERROR sqlprint;
+
+int
+main ()
+{
+EXEC SQL BEGIN DECLARE SECTION;
+  char json[1024];
+EXEC SQL END DECLARE SECTION;
+
+  ECPGdebug (1, stderr);
+
+  EXEC SQL CONNECT TO REGRESSDB1;
+  EXEC SQL SET AUTOCOMMIT = ON;
+
+  EXEC SQL SELECT JSON_OBJECT(RETURNING text) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_OBJECT(RETURNING text FORMAT JSON) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_ARRAY(RETURNING jsonb) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON) INTO :json;
+  printf("Found json=%s\n", json);
+
+  EXEC SQL SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE) INTO :json;
+  // error
+
+  EXEC SQL SELECT JSON_OBJECT(1: 1, '2': NULL, 1: '2' ABSENT ON NULL WITHOUT UNIQUE KEYS) INTO :json;
+  printf("Found json=%s\n", json);
+
+  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 DISCONNECT;
+
+  return 0;
+}
index 56dba9d6e28b6b590ea1e56c8e013a9b230ba774..aa29bc597bde29fd36097f0416f601e5259ed60d 100644 (file)
@@ -2119,8 +2119,7 @@ SELECT json_build_object('a', 'b', 'c'); -- error
 ERROR:  argument list must have even number of elements
 HINT:  The arguments of json_build_object() must consist of alternating keys and values.
 SELECT json_build_object(NULL, 'a'); -- error, key cannot be NULL
-ERROR:  argument 1 cannot be null
-HINT:  Object keys should be text.
+ERROR:  null value not allowed for object key
 SELECT json_build_object('a', NULL); -- ok
  json_build_object 
 -------------------
@@ -2149,8 +2148,7 @@ SELECT json_build_object(VARIADIC ARRAY['a', NULL]::text[]); -- ok
 (1 row)
 
 SELECT json_build_object(VARIADIC ARRAY[NULL, 'a']::text[]); -- error, key cannot be NULL
-ERROR:  argument 1 cannot be null
-HINT:  Object keys should be text.
+ERROR:  null value not allowed for object key
 SELECT json_build_object(VARIADIC '{1,2,3,4}'::text[]); -- ok
    json_build_object    
 ------------------------
@@ -2191,8 +2189,7 @@ SELECT json_build_object(1,2);
 
 -- keys must be scalar and not null
 SELECT json_build_object(null,2);
-ERROR:  argument 1 cannot be null
-HINT:  Object keys should be text.
+ERROR:  null value not allowed for object key
 SELECT json_build_object(r,2) FROM (SELECT 1 AS a, 2 AS b) r;
 ERROR:  key value must be scalar, not array, composite, or json
 SELECT json_build_object(json '{"a":1,"b":2}', 3);
@@ -2218,7 +2215,7 @@ SELECT json_object_agg(name, type) FROM foo;
 
 INSERT INTO foo VALUES (999999, NULL, 'bar');
 SELECT json_object_agg(name, type) FROM foo;
-ERROR:  field name must not be null
+ERROR:  null value not allowed for object key
 -- json_object
 -- empty object, one dimension
 SELECT json_object('{}');
index 02f5348ab1b5e8e9e25e186eef2df056d1da97b5..a1bdf2c0b5f81abdd8de071aa37373087f9101d3 100644 (file)
@@ -1505,8 +1505,10 @@ WHERE a.aggfnoid = p.oid AND
          NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2]))
      OR (p.pronargs > 2 AND
          NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3]))
-     -- we could carry the check further, but 3 args is enough for now
-     OR (p.pronargs > 3)
+     OR (p.pronargs > 3 AND
+         NOT binary_coercible(p.proargtypes[3], ptr.proargtypes[4]))
+     -- we could carry the check further, but 4 args is enough for now
+     OR (p.pronargs > 4)
     );
  aggfnoid | proname | oid | proname 
 ----------+---------+-----+---------
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
new file mode 100644 (file)
index 0000000..c4bfe80
--- /dev/null
@@ -0,0 +1,756 @@
+-- JSON_OBJECT()
+SELECT JSON_OBJECT();
+ json_object 
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING json);
+ json_object 
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING json FORMAT JSON);
+ json_object 
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING jsonb);
+ json_object 
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSON);
+ json_object 
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING text);
+ json_object 
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING text FORMAT JSON);
+ json_object 
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING UTF8);
+ERROR:  cannot set JSON encoding for non-bytea output types
+LINE 1: SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING UTF8)...
+                                          ^
+SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING);
+ERROR:  unrecognized JSON encoding: invalid_encoding
+SELECT JSON_OBJECT(RETURNING bytea);
+ json_object 
+-------------
+ \x7b7d
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON);
+ json_object 
+-------------
+ \x7b7d
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF8);
+ json_object 
+-------------
+ \x7b7d
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF16);
+ERROR:  unsupported JSON encoding
+LINE 1: SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF1...
+                                           ^
+HINT:  Only UTF8 JSON encoding is supported.
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF32);
+ERROR:  unsupported JSON encoding
+LINE 1: SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF3...
+                                           ^
+HINT:  Only UTF8 JSON encoding is supported.
+SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON);
+ERROR:  cannot use non-string types with explicit FORMAT JSON clause
+LINE 1: SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON);
+                                            ^
+SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON ENCODING UTF8);
+ERROR:  JSON ENCODING clause is only allowed for bytea input type
+LINE 1: SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON ENCODING UTF...
+                                            ^
+SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON);
+WARNING:  FORMAT JSON has no effect for json and jsonb types
+LINE 1: SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON);
+                                             ^
+  json_object   
+----------------
+ {"foo" : null}
+(1 row)
+
+SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON ENCODING UTF8);
+ERROR:  JSON ENCODING clause is only allowed for bytea input type
+LINE 1: SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON ENCODING UT...
+                                             ^
+SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON);
+WARNING:  FORMAT JSON has no effect for json and jsonb types
+LINE 1: SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON);
+                                              ^
+  json_object  
+---------------
+ {"foo": null}
+(1 row)
+
+SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING UTF8);
+ERROR:  JSON ENCODING clause is only allowed for bytea input type
+LINE 1: SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING U...
+                                              ^
+SELECT JSON_OBJECT(NULL: 1);
+ERROR:  null value not allowed for object key
+SELECT JSON_OBJECT('a': 2 + 3);
+ json_object 
+-------------
+ {"a" : 5}
+(1 row)
+
+SELECT JSON_OBJECT('a' VALUE 2 + 3);
+ json_object 
+-------------
+ {"a" : 5}
+(1 row)
+
+--SELECT JSON_OBJECT(KEY 'a' VALUE 2 + 3);
+SELECT JSON_OBJECT('a' || 2: 1);
+ json_object 
+-------------
+ {"a2" : 1}
+(1 row)
+
+SELECT JSON_OBJECT(('a' || 2) VALUE 1);
+ json_object 
+-------------
+ {"a2" : 1}
+(1 row)
+
+--SELECT JSON_OBJECT('a' || 2 VALUE 1);
+--SELECT JSON_OBJECT(KEY 'a' || 2 VALUE 1);
+SELECT JSON_OBJECT('a': 2::text);
+ json_object 
+-------------
+ {"a" : "2"}
+(1 row)
+
+SELECT JSON_OBJECT('a' VALUE 2::text);
+ json_object 
+-------------
+ {"a" : "2"}
+(1 row)
+
+--SELECT JSON_OBJECT(KEY 'a' VALUE 2::text);
+SELECT JSON_OBJECT(1::text: 2);
+ json_object 
+-------------
+ {"1" : 2}
+(1 row)
+
+SELECT JSON_OBJECT((1::text) VALUE 2);
+ json_object 
+-------------
+ {"1" : 2}
+(1 row)
+
+--SELECT JSON_OBJECT(1::text VALUE 2);
+--SELECT JSON_OBJECT(KEY 1::text VALUE 2);
+SELECT JSON_OBJECT(json '[1]': 123);
+ERROR:  key value must be scalar, not array, composite, or json
+SELECT JSON_OBJECT(ARRAY[1,2,3]: 'aaa');
+ERROR:  key value must be scalar, not array, composite, or json
+SELECT JSON_OBJECT(
+       'a': '123',
+       1.23: 123,
+       'c': json '[ 1,true,{ } ]',
+       'd': jsonb '{ "x" : 123.45 }'
+);
+                            json_object                            
+-------------------------------------------------------------------
+ {"a": "123", "c": [1, true, {}], "d": {"x": 123.45}, "1.23": 123}
+(1 row)
+
+SELECT JSON_OBJECT(
+       'a': '123',
+       1.23: 123,
+       'c': json '[ 1,true,{ } ]',
+       'd': jsonb '{ "x" : 123.45 }'
+       RETURNING jsonb
+);
+                            json_object                            
+-------------------------------------------------------------------
+ {"a": "123", "c": [1, true, {}], "d": {"x": 123.45}, "1.23": 123}
+(1 row)
+
+/*
+SELECT JSON_OBJECT(
+       'a': '123',
+       KEY 1.23 VALUE 123,
+       'c' VALUE json '[1, true, {}]'
+);
+*/
+SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa'));
+                  json_object                  
+-----------------------------------------------
+ {"a" : "123", "b" : {"a" : 111, "b" : "aaa"}}
+(1 row)
+
+SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa' RETURNING jsonb));
+                json_object                
+-------------------------------------------
+ {"a": "123", "b": {"a": 111, "b": "aaa"}}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text));
+      json_object      
+-----------------------
+ {"a" : "{\"b\" : 1}"}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text) FORMAT JSON);
+    json_object    
+-------------------
+ {"a" : {"b" : 1}}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea));
+           json_object           
+---------------------------------
+ {"a" : "\\x7b226222203a20317d"}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea) FORMAT JSON);
+    json_object    
+-------------------
+ {"a" : {"b" : 1}}
+(1 row)
+
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2);
+           json_object            
+----------------------------------
+ {"a" : "1", "b" : null, "c" : 2}
+(1 row)
+
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 NULL ON NULL);
+           json_object            
+----------------------------------
+ {"a" : "1", "b" : null, "c" : 2}
+(1 row)
+
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 ABSENT ON NULL);
+     json_object      
+----------------------
+ {"a" : "1", "c" : 2}
+(1 row)
+
+SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE);
+ERROR:  duplicate JSON key "1"
+SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE);
+ERROR:  duplicate JSON key "1"
+SELECT JSON_OBJECT(1: 1, '1': NULL NULL ON NULL WITH UNIQUE RETURNING jsonb);
+ERROR:  duplicate JSON object key
+SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+ERROR:  duplicate JSON object key
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 NULL ON NULL WITH UNIQUE);
+ERROR:  duplicate JSON key "1"
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE);
+ERROR:  duplicate JSON key "1"
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE);
+    json_object     
+--------------------
+ {"1" : 1, "1" : 1}
+(1 row)
+
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+ERROR:  duplicate JSON object key
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE RETURNING jsonb);
+ json_object 
+-------------
+ {"1": 1}
+(1 row)
+
+SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+        json_object         
+----------------------------
+ {"1": 1, "3": 1, "5": "a"}
+(1 row)
+
+-- JSON_ARRAY()
+SELECT JSON_ARRAY();
+ json_array 
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING json);
+ json_array 
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING json FORMAT JSON);
+ json_array 
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING jsonb);
+ json_array 
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON);
+ json_array 
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING text);
+ json_array 
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING text FORMAT JSON);
+ json_array 
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING UTF8);
+ERROR:  cannot set JSON encoding for non-bytea output types
+LINE 1: SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING UTF8);
+                                         ^
+SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING);
+ERROR:  unrecognized JSON encoding: invalid_encoding
+SELECT JSON_ARRAY(RETURNING bytea);
+ json_array 
+------------
+ \x5b5d
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON);
+ json_array 
+------------
+ \x5b5d
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF8);
+ json_array 
+------------
+ \x5b5d
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF16);
+ERROR:  unsupported JSON encoding
+LINE 1: SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF16...
+                                          ^
+HINT:  Only UTF8 JSON encoding is supported.
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF32);
+ERROR:  unsupported JSON encoding
+LINE 1: SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF32...
+                                          ^
+HINT:  Only UTF8 JSON encoding is supported.
+SELECT JSON_ARRAY('aaa', 111, true, array[1,2,3], NULL, json '{"a": [1]}', jsonb '["a",3]');
+                     json_array                      
+-----------------------------------------------------
+ ["aaa", 111, true, [1, 2, 3], {"a": [1]}, ["a", 3]]
+(1 row)
+
+SELECT JSON_ARRAY('a',  NULL, 'b' NULL   ON NULL);
+    json_array    
+------------------
+ ["a", null, "b"]
+(1 row)
+
+SELECT JSON_ARRAY('a',  NULL, 'b' ABSENT ON NULL);
+ json_array 
+------------
+ ["a", "b"]
+(1 row)
+
+SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL);
+ json_array 
+------------
+ ["b"]
+(1 row)
+
+SELECT JSON_ARRAY('a',  NULL, 'b' NULL   ON NULL RETURNING jsonb);
+    json_array    
+------------------
+ ["a", null, "b"]
+(1 row)
+
+SELECT JSON_ARRAY('a',  NULL, 'b' ABSENT ON NULL RETURNING jsonb);
+ json_array 
+------------
+ ["a", "b"]
+(1 row)
+
+SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL RETURNING jsonb);
+ json_array 
+------------
+ ["b"]
+(1 row)
+
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' RETURNING text));
+          json_array           
+-------------------------------
+ ["[\"{ \\\"a\\\" : 123 }\"]"]
+(1 row)
+
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text));
+      json_array       
+-----------------------
+ ["[{ \"a\" : 123 }]"]
+(1 row)
+
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSON);
+    json_array     
+-------------------
+ [[{ "a" : 123 }]]
+(1 row)
+
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i));
+ json_array 
+------------
+ [1, 2, 4]
+(1 row)
+
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i));
+ json_array 
+------------
+ [[1,2],   +
+  [3,4]]
+(1 row)
+
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) RETURNING jsonb);
+    json_array    
+------------------
+ [[1, 2], [3, 4]]
+(1 row)
+
+--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL);
+--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL RETURNING jsonb);
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (3), (1), (NULL), (2)) foo(i) ORDER BY i);
+ json_array 
+------------
+ [1, 2, 3]
+(1 row)
+
+-- Should fail
+SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i));
+ERROR:  subquery must return only one column
+LINE 1: SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i));
+               ^
+SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i));
+ERROR:  subquery must return only one column
+LINE 1: SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i));
+               ^
+SELECT JSON_ARRAY(SELECT * FROM (VALUES (1, 2)) foo(i, j));
+ERROR:  subquery must return only one column
+LINE 1: SELECT JSON_ARRAY(SELECT * FROM (VALUES (1, 2)) foo(i, j));
+               ^
+-- JSON_ARRAYAGG()
+SELECT JSON_ARRAYAGG(i) IS NULL,
+               JSON_ARRAYAGG(i RETURNING jsonb) IS NULL
+FROM generate_series(1, 0) i;
+ ?column? | ?column? 
+----------+----------
+ t        | t
+(1 row)
+
+SELECT JSON_ARRAYAGG(i),
+               JSON_ARRAYAGG(i RETURNING jsonb)
+FROM generate_series(1, 5) i;
+  json_arrayagg  |  json_arrayagg  
+-----------------+-----------------
+ [1, 2, 3, 4, 5] | [1, 2, 3, 4, 5]
+(1 row)
+
+SELECT JSON_ARRAYAGG(i ORDER BY i DESC)
+FROM generate_series(1, 5) i;
+  json_arrayagg  
+-----------------
+ [5, 4, 3, 2, 1]
+(1 row)
+
+SELECT JSON_ARRAYAGG(i::text::json)
+FROM generate_series(1, 5) i;
+  json_arrayagg  
+-----------------
+ [1, 2, 3, 4, 5]
+(1 row)
+
+SELECT JSON_ARRAYAGG(JSON_ARRAY(i, i + 1 RETURNING text) FORMAT JSON)
+FROM generate_series(1, 5) i;
+              json_arrayagg               
+------------------------------------------
+ [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6]]
+(1 row)
+
+SELECT JSON_ARRAYAGG(NULL),
+               JSON_ARRAYAGG(NULL RETURNING jsonb)
+FROM generate_series(1, 5);
+ json_arrayagg | json_arrayagg 
+---------------+---------------
+ []            | []
+(1 row)
+
+SELECT JSON_ARRAYAGG(NULL NULL ON NULL),
+               JSON_ARRAYAGG(NULL NULL ON NULL RETURNING jsonb)
+FROM generate_series(1, 5);
+         json_arrayagg          |         json_arrayagg          
+--------------------------------+--------------------------------
+ [null, null, null, null, null] | [null, null, null, null, null]
+(1 row)
+
+\x
+SELECT
+       JSON_ARRAYAGG(bar) as no_options,
+       JSON_ARRAYAGG(bar RETURNING jsonb) as returning_jsonb,
+       JSON_ARRAYAGG(bar ABSENT ON NULL) as absent_on_null,
+       JSON_ARRAYAGG(bar ABSENT ON NULL RETURNING jsonb) as absentonnull_returning_jsonb,
+       JSON_ARRAYAGG(bar NULL ON NULL) as null_on_null,
+       JSON_ARRAYAGG(bar NULL ON NULL RETURNING jsonb) as nullonnull_returning_jsonb,
+       JSON_ARRAYAGG(foo) as row_no_options,
+       JSON_ARRAYAGG(foo RETURNING jsonb) as row_returning_jsonb,
+       JSON_ARRAYAGG(foo ORDER BY bar) FILTER (WHERE bar > 2) as row_filtered_agg,
+       JSON_ARRAYAGG(foo ORDER BY bar RETURNING jsonb) FILTER (WHERE bar > 2) as row_filtered_agg_returning_jsonb
+FROM
+       (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL)) foo(bar);
+-[ RECORD 1 ]--------------------+-------------------------------------------------------------------------------------------------------------------------
+no_options                       | [1, 2, 3, 4, 5]
+returning_jsonb                  | [1, 2, 3, 4, 5]
+absent_on_null                   | [1, 2, 3, 4, 5]
+absentonnull_returning_jsonb     | [1, 2, 3, 4, 5]
+null_on_null                     | [1, 2, 3, 4, 5, null, null, null, null]
+nullonnull_returning_jsonb       | [1, 2, 3, 4, 5, null, null, null, null]
+row_no_options                   | [{"bar":1},                                                                                                             +
+                                 |  {"bar":2},                                                                                                             +
+                                 |  {"bar":3},                                                                                                             +
+                                 |  {"bar":4},                                                                                                             +
+                                 |  {"bar":5},                                                                                                             +
+                                 |  {"bar":null},                                                                                                          +
+                                 |  {"bar":null},                                                                                                          +
+                                 |  {"bar":null},                                                                                                          +
+                                 |  {"bar":null}]
+row_returning_jsonb              | [{"bar": 1}, {"bar": 2}, {"bar": 3}, {"bar": 4}, {"bar": 5}, {"bar": null}, {"bar": null}, {"bar": null}, {"bar": null}]
+row_filtered_agg                 | [{"bar":3},                                                                                                             +
+                                 |  {"bar":4},                                                                                                             +
+                                 |  {"bar":5}]
+row_filtered_agg_returning_jsonb | [{"bar": 3}, {"bar": 4}, {"bar": 5}]
+
+\x
+SELECT
+       bar, JSON_ARRAYAGG(bar) FILTER (WHERE bar > 2) OVER (PARTITION BY foo.bar % 2)
+FROM
+       (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL), (5), (4)) foo(bar);
+ bar | json_arrayagg 
+-----+---------------
+   4 | [4, 4]
+   4 | [4, 4]
+   2 | [4, 4]
+   5 | [5, 3, 5]
+   3 | [5, 3, 5]
+   1 | [5, 3, 5]
+   5 | [5, 3, 5]
+     | 
+     | 
+     | 
+     | 
+(11 rows)
+
+-- JSON_OBJECTAGG()
+SELECT JSON_OBJECTAGG('key': 1) IS NULL,
+               JSON_OBJECTAGG('key': 1 RETURNING jsonb) IS NULL
+WHERE FALSE;
+ ?column? | ?column? 
+----------+----------
+ t        | t
+(1 row)
+
+SELECT JSON_OBJECTAGG(NULL: 1);
+ERROR:  null value not allowed for object key
+SELECT JSON_OBJECTAGG(NULL: 1 RETURNING jsonb);
+ERROR:  field name must not be null
+SELECT
+       JSON_OBJECTAGG(i: i),
+--     JSON_OBJECTAGG(i VALUE i),
+--     JSON_OBJECTAGG(KEY i VALUE i),
+       JSON_OBJECTAGG(i: i RETURNING jsonb)
+FROM
+       generate_series(1, 5) i;
+                 json_objectagg                  |              json_objectagg              
+-------------------------------------------------+------------------------------------------
+ { "1" : 1, "2" : 2, "3" : 3, "4" : 4, "5" : 5 } | {"1": 1, "2": 2, "3": 3, "4": 4, "5": 5}
+(1 row)
+
+SELECT
+       JSON_OBJECTAGG(k: v),
+       JSON_OBJECTAGG(k: v NULL ON NULL),
+       JSON_OBJECTAGG(k: v ABSENT ON NULL),
+       JSON_OBJECTAGG(k: v RETURNING jsonb),
+       JSON_OBJECTAGG(k: v NULL ON NULL RETURNING jsonb),
+       JSON_OBJECTAGG(k: v ABSENT ON NULL RETURNING jsonb)
+FROM
+       (VALUES (1, 1), (1, NULL), (2, NULL), (3, 3)) foo(k, v);
+                json_objectagg                |                json_objectagg                |    json_objectagg    |         json_objectagg         |         json_objectagg         |  json_objectagg  
+----------------------------------------------+----------------------------------------------+----------------------+--------------------------------+--------------------------------+------------------
+ { "1" : 1, "1" : null, "2" : null, "3" : 3 } | { "1" : 1, "1" : null, "2" : null, "3" : 3 } | { "1" : 1, "3" : 3 } | {"1": null, "2": null, "3": 3} | {"1": null, "2": null, "3": 3} | {"1": 1, "3": 3}
+(1 row)
+
+SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+ERROR:  duplicate JSON key "1"
+SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+ERROR:  duplicate JSON key "1"
+SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (0, NULL), (3, NULL), (2, 2), (4, NULL)) foo(k, v);
+    json_objectagg    
+----------------------
+ { "1" : 1, "2" : 2 }
+(1 row)
+
+SELECT JSON_OBJECTAGG(k: v 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), (1, NULL), (2, 2)) foo(k, v);
+ERROR:  duplicate JSON object key
+-- Test JSON_OBJECT deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Result
+   Output: JSON_OBJECT('foo' : '1'::json, 'bar' : 'baz'::text RETURNING json)
+(2 rows)
+
+CREATE VIEW json_object_view AS
+SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);
+\sv json_object_view
+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;
+-- Test JSON_ARRAY deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Result
+   Output: JSON_ARRAY('1'::json, 2 RETURNING json)
+(2 rows)
+
+CREATE VIEW json_array_view AS
+SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json);
+\sv json_array_view
+CREATE OR REPLACE VIEW public.json_array_view AS
+ SELECT JSON_ARRAY('1'::text FORMAT JSON, 2 RETURNING json) AS "json_array"
+DROP VIEW json_array_view;
+-- Test JSON_OBJECTAGG deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+                                                              QUERY PLAN                                                              
+--------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   Output: JSON_OBJECTAGG(i : (('111'::text || (i)::text))::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE (i > 3))
+   ->  Function Scan on pg_catalog.generate_series i
+         Output: i
+         Function Call: generate_series(1, 5)
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) OVER (PARTITION BY i % 2)
+FROM generate_series(1,5) i;
+                                                            QUERY PLAN                                                             
+-----------------------------------------------------------------------------------------------------------------------------------
+ WindowAgg
+   Output: JSON_OBJECTAGG(i : (('111'::text || (i)::text))::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) OVER (?), ((i % 2))
+   ->  Sort
+         Output: ((i % 2)), i
+         Sort Key: ((i.i % 2))
+         ->  Function Scan on pg_catalog.generate_series i
+               Output: (i % 2), i
+               Function Call: generate_series(1, 5)
+(8 rows)
+
+CREATE VIEW json_objectagg_view AS
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+\sv json_objectagg_view
+CREATE OR REPLACE VIEW public.json_objectagg_view AS
+ SELECT JSON_OBJECTAGG(i : ('111'::text || i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i > 3) AS "json_objectagg"
+   FROM generate_series(1, 5) i(i)
+DROP VIEW json_objectagg_view;
+-- Test JSON_ARRAYAGG deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   Output: JSON_ARRAYAGG((('111'::text || (i)::text))::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE (i > 3))
+   ->  Function Scan on pg_catalog.generate_series i
+         Output: i
+         Function Call: generate_series(1, 5)
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (PARTITION BY i % 2)
+FROM generate_series(1,5) i;
+                                                        QUERY PLAN                                                        
+--------------------------------------------------------------------------------------------------------------------------
+ WindowAgg
+   Output: JSON_ARRAYAGG((('111'::text || (i)::text))::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (?), ((i % 2))
+   ->  Sort
+         Output: ((i % 2)), i
+         Sort Key: ((i.i % 2))
+         ->  Function Scan on pg_catalog.generate_series i
+               Output: (i % 2), i
+               Function Call: generate_series(1, 5)
+(8 rows)
+
+CREATE VIEW json_arrayagg_view AS
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+\sv json_arrayagg_view
+CREATE OR REPLACE VIEW public.json_arrayagg_view AS
+ SELECT JSON_ARRAYAGG(('111'::text || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) AS "json_arrayagg"
+   FROM generate_series(1, 5) i(i)
+DROP VIEW json_arrayagg_view;
+-- Test JSON_ARRAY(subquery) deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb);
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Result
+   Output: $0
+   InitPlan 1 (returns $0)
+     ->  Aggregate
+           Output: JSON_ARRAYAGG("*VALUES*".column1 RETURNING jsonb)
+           ->  Values Scan on "*VALUES*"
+                 Output: "*VALUES*".column1
+(7 rows)
+
+CREATE VIEW json_array_subquery_view AS
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb);
+\sv json_array_subquery_view
+CREATE OR REPLACE VIEW public.json_array_subquery_view AS
+ SELECT ( SELECT JSON_ARRAYAGG(q.a RETURNING jsonb) AS "json_arrayagg"
+           FROM ( SELECT foo.i
+                   FROM ( VALUES (1), (2), (NULL::integer), (4)) foo(i)) q(a)) AS "json_array"
+DROP VIEW json_array_subquery_view;
index 15e015b3d64bd40d0a359b6e9235702d3685394d..3624035639311d9cb09c4e307ca038dce50ce212 100644 (file)
@@ -103,7 +103,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # ----------
 # Another group of parallel tests (JSON related)
 # ----------
-test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath
+test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
 
 # ----------
 # Another group of parallel tests
index 56b54ba9883dd27be415af311fc169d28e7e12e4..e2d2c70d70679c1e23d153b06940de21bd406dcb 100644 (file)
@@ -880,8 +880,10 @@ WHERE a.aggfnoid = p.oid AND
          NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2]))
      OR (p.pronargs > 2 AND
          NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3]))
-     -- we could carry the check further, but 3 args is enough for now
-     OR (p.pronargs > 3)
+     OR (p.pronargs > 3 AND
+         NOT binary_coercible(p.proargtypes[3], ptr.proargtypes[4]))
+     -- we could carry the check further, but 4 args is enough for now
+     OR (p.pronargs > 4)
     );
 
 -- Cross-check finalfn (if present) against its entry in pg_proc.
diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
new file mode 100644 (file)
index 0000000..fbbf6a6
--- /dev/null
@@ -0,0 +1,284 @@
+-- JSON_OBJECT()
+SELECT JSON_OBJECT();
+SELECT JSON_OBJECT(RETURNING json);
+SELECT JSON_OBJECT(RETURNING json FORMAT JSON);
+SELECT JSON_OBJECT(RETURNING jsonb);
+SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSON);
+SELECT JSON_OBJECT(RETURNING text);
+SELECT JSON_OBJECT(RETURNING text FORMAT JSON);
+SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING UTF8);
+SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING);
+SELECT JSON_OBJECT(RETURNING bytea);
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON);
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF8);
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF16);
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF32);
+
+SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON);
+SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON ENCODING UTF8);
+SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON);
+SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON ENCODING UTF8);
+SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON);
+SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING UTF8);
+
+SELECT JSON_OBJECT(NULL: 1);
+SELECT JSON_OBJECT('a': 2 + 3);
+SELECT JSON_OBJECT('a' VALUE 2 + 3);
+--SELECT JSON_OBJECT(KEY 'a' VALUE 2 + 3);
+SELECT JSON_OBJECT('a' || 2: 1);
+SELECT JSON_OBJECT(('a' || 2) VALUE 1);
+--SELECT JSON_OBJECT('a' || 2 VALUE 1);
+--SELECT JSON_OBJECT(KEY 'a' || 2 VALUE 1);
+SELECT JSON_OBJECT('a': 2::text);
+SELECT JSON_OBJECT('a' VALUE 2::text);
+--SELECT JSON_OBJECT(KEY 'a' VALUE 2::text);
+SELECT JSON_OBJECT(1::text: 2);
+SELECT JSON_OBJECT((1::text) VALUE 2);
+--SELECT JSON_OBJECT(1::text VALUE 2);
+--SELECT JSON_OBJECT(KEY 1::text VALUE 2);
+SELECT JSON_OBJECT(json '[1]': 123);
+SELECT JSON_OBJECT(ARRAY[1,2,3]: 'aaa');
+
+SELECT JSON_OBJECT(
+       'a': '123',
+       1.23: 123,
+       'c': json '[ 1,true,{ } ]',
+       'd': jsonb '{ "x" : 123.45 }'
+);
+
+SELECT JSON_OBJECT(
+       'a': '123',
+       1.23: 123,
+       'c': json '[ 1,true,{ } ]',
+       'd': jsonb '{ "x" : 123.45 }'
+       RETURNING jsonb
+);
+
+/*
+SELECT JSON_OBJECT(
+       'a': '123',
+       KEY 1.23 VALUE 123,
+       'c' VALUE json '[1, true, {}]'
+);
+*/
+
+SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa'));
+SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa' RETURNING jsonb));
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text));
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text) FORMAT JSON);
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea));
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea) FORMAT JSON);
+
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2);
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 NULL ON NULL);
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 ABSENT ON NULL);
+
+SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE);
+SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE);
+SELECT JSON_OBJECT(1: 1, '1': NULL NULL ON NULL WITH UNIQUE RETURNING jsonb);
+SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 NULL ON NULL WITH UNIQUE);
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE);
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE);
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE RETURNING jsonb);
+SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+
+
+-- JSON_ARRAY()
+SELECT JSON_ARRAY();
+SELECT JSON_ARRAY(RETURNING json);
+SELECT JSON_ARRAY(RETURNING json FORMAT JSON);
+SELECT JSON_ARRAY(RETURNING jsonb);
+SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON);
+SELECT JSON_ARRAY(RETURNING text);
+SELECT JSON_ARRAY(RETURNING text FORMAT JSON);
+SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING UTF8);
+SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING);
+SELECT JSON_ARRAY(RETURNING bytea);
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON);
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF8);
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF16);
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF32);
+
+SELECT JSON_ARRAY('aaa', 111, true, array[1,2,3], NULL, json '{"a": [1]}', jsonb '["a",3]');
+
+SELECT JSON_ARRAY('a',  NULL, 'b' NULL   ON NULL);
+SELECT JSON_ARRAY('a',  NULL, 'b' ABSENT ON NULL);
+SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL);
+SELECT JSON_ARRAY('a',  NULL, 'b' NULL   ON NULL RETURNING jsonb);
+SELECT JSON_ARRAY('a',  NULL, 'b' ABSENT ON NULL RETURNING jsonb);
+SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL RETURNING jsonb);
+
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' RETURNING text));
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text));
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSON);
+
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i));
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i));
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) RETURNING jsonb);
+--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL);
+--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL RETURNING jsonb);
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (3), (1), (NULL), (2)) foo(i) ORDER BY i);
+-- Should fail
+SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i));
+SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i));
+SELECT JSON_ARRAY(SELECT * FROM (VALUES (1, 2)) foo(i, j));
+
+-- JSON_ARRAYAGG()
+SELECT JSON_ARRAYAGG(i) IS NULL,
+               JSON_ARRAYAGG(i RETURNING jsonb) IS NULL
+FROM generate_series(1, 0) i;
+
+SELECT JSON_ARRAYAGG(i),
+               JSON_ARRAYAGG(i RETURNING jsonb)
+FROM generate_series(1, 5) i;
+
+SELECT JSON_ARRAYAGG(i ORDER BY i DESC)
+FROM generate_series(1, 5) i;
+
+SELECT JSON_ARRAYAGG(i::text::json)
+FROM generate_series(1, 5) i;
+
+SELECT JSON_ARRAYAGG(JSON_ARRAY(i, i + 1 RETURNING text) FORMAT JSON)
+FROM generate_series(1, 5) i;
+
+SELECT JSON_ARRAYAGG(NULL),
+               JSON_ARRAYAGG(NULL RETURNING jsonb)
+FROM generate_series(1, 5);
+
+SELECT JSON_ARRAYAGG(NULL NULL ON NULL),
+               JSON_ARRAYAGG(NULL NULL ON NULL RETURNING jsonb)
+FROM generate_series(1, 5);
+
+\x
+SELECT
+       JSON_ARRAYAGG(bar) as no_options,
+       JSON_ARRAYAGG(bar RETURNING jsonb) as returning_jsonb,
+       JSON_ARRAYAGG(bar ABSENT ON NULL) as absent_on_null,
+       JSON_ARRAYAGG(bar ABSENT ON NULL RETURNING jsonb) as absentonnull_returning_jsonb,
+       JSON_ARRAYAGG(bar NULL ON NULL) as null_on_null,
+       JSON_ARRAYAGG(bar NULL ON NULL RETURNING jsonb) as nullonnull_returning_jsonb,
+       JSON_ARRAYAGG(foo) as row_no_options,
+       JSON_ARRAYAGG(foo RETURNING jsonb) as row_returning_jsonb,
+       JSON_ARRAYAGG(foo ORDER BY bar) FILTER (WHERE bar > 2) as row_filtered_agg,
+       JSON_ARRAYAGG(foo ORDER BY bar RETURNING jsonb) FILTER (WHERE bar > 2) as row_filtered_agg_returning_jsonb
+FROM
+       (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL)) foo(bar);
+\x
+
+SELECT
+       bar, JSON_ARRAYAGG(bar) FILTER (WHERE bar > 2) OVER (PARTITION BY foo.bar % 2)
+FROM
+       (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL), (5), (4)) foo(bar);
+
+-- JSON_OBJECTAGG()
+SELECT JSON_OBJECTAGG('key': 1) IS NULL,
+               JSON_OBJECTAGG('key': 1 RETURNING jsonb) IS NULL
+WHERE FALSE;
+
+SELECT JSON_OBJECTAGG(NULL: 1);
+
+SELECT JSON_OBJECTAGG(NULL: 1 RETURNING jsonb);
+
+SELECT
+       JSON_OBJECTAGG(i: i),
+--     JSON_OBJECTAGG(i VALUE i),
+--     JSON_OBJECTAGG(KEY i VALUE i),
+       JSON_OBJECTAGG(i: i RETURNING jsonb)
+FROM
+       generate_series(1, 5) i;
+
+SELECT
+       JSON_OBJECTAGG(k: v),
+       JSON_OBJECTAGG(k: v NULL ON NULL),
+       JSON_OBJECTAGG(k: v ABSENT ON NULL),
+       JSON_OBJECTAGG(k: v RETURNING jsonb),
+       JSON_OBJECTAGG(k: v NULL ON NULL RETURNING jsonb),
+       JSON_OBJECTAGG(k: v ABSENT ON NULL RETURNING jsonb)
+FROM
+       (VALUES (1, 1), (1, NULL), (2, NULL), (3, 3)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (0, NULL), (3, NULL), (2, 2), (4, NULL)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v 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), (1, 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);
+
+CREATE VIEW json_object_view AS
+SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);
+
+\sv json_object_view
+
+DROP VIEW json_object_view;
+
+-- Test JSON_ARRAY deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json);
+
+CREATE VIEW json_array_view AS
+SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json);
+
+\sv json_array_view
+
+DROP VIEW json_array_view;
+
+-- Test JSON_OBJECTAGG deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) OVER (PARTITION BY i % 2)
+FROM generate_series(1,5) i;
+
+CREATE VIEW json_objectagg_view AS
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+
+\sv json_objectagg_view
+
+DROP VIEW json_objectagg_view;
+
+-- Test JSON_ARRAYAGG deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (PARTITION BY i % 2)
+FROM generate_series(1,5) i;
+
+CREATE VIEW json_arrayagg_view AS
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+
+\sv json_arrayagg_view
+
+DROP VIEW json_arrayagg_view;
+
+-- Test JSON_ARRAY(subquery) deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb);
+
+CREATE VIEW json_array_subquery_view AS
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb);
+
+\sv json_array_subquery_view
+
+DROP VIEW json_array_subquery_view;
index f45bcc52d39543fbb548fbffa487a0fd312daf54..f5cd394b335a666dbf19054d1c51f94c6c941f03 100644 (file)
@@ -1247,6 +1247,7 @@ JsonBehaviorType
 JsonCoercion
 JsonCommon
 JsonConstructorExpr
+JsonConstructorExprState
 JsonConstructorType
 JsonEncoding
 JsonExpr