($INBOX_DIR/description missing)
help / color / mirror / Atom feedFrom: Andrew Dunstan <[email protected]>
Subject: [PATCH 1/4] SQL/JSON functions
Date: Thu, 16 Sep 2021 10:28:55 -0400
Common SQL/JSON clauses
SQL/JSON constructors
IS JSON predicate
SQL/JSON query functions
SQL/JSON functions for json type
GUC sql_json
---
doc/src/sgml/config.sgml | 19 +
doc/src/sgml/func.sgml | 2027 +++++++++++++++++
doc/src/sgml/keywords/sql2016-02-reserved.txt | 3 +
src/backend/executor/execExpr.c | 349 ++-
src/backend/executor/execExprInterp.c | 726 ++++++
src/backend/jit/llvm/llvmjit_expr.c | 18 +
src/backend/jit/llvm/llvmjit_types.c | 3 +
src/backend/nodes/copyfuncs.c | 433 ++++
src/backend/nodes/equalfuncs.c | 293 +++
src/backend/nodes/makefuncs.c | 103 +
src/backend/nodes/nodeFuncs.c | 427 ++++
src/backend/nodes/outfuncs.c | 141 ++
src/backend/nodes/readfuncs.c | 177 ++
src/backend/optimizer/path/costsize.c | 3 +-
src/backend/optimizer/util/clauses.c | 78 +
src/backend/parser/gram.y | 767 ++++++-
src/backend/parser/parse_collate.c | 4 +
src/backend/parser/parse_expr.c | 1471 ++++++++++++
src/backend/parser/parse_target.c | 37 +
src/backend/parser/parser.c | 16 +
src/backend/utils/adt/format_type.c | 9 +
src/backend/utils/adt/formatting.c | 45 +-
src/backend/utils/adt/json.c | 572 ++++-
src/backend/utils/adt/jsonb.c | 352 ++-
src/backend/utils/adt/jsonb_util.c | 24 +-
src/backend/utils/adt/jsonfuncs.c | 70 +-
src/backend/utils/adt/jsonpath.c | 257 +++
src/backend/utils/adt/jsonpath_exec.c | 350 ++-
src/backend/utils/adt/ruleutils.c | 437 +++-
src/backend/utils/misc/guc.c | 19 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/backend/utils/misc/queryjumble.c | 69 +
src/include/catalog/pg_aggregate.dat | 22 +
src/include/catalog/pg_proc.dat | 70 +
src/include/executor/execExpr.h | 81 +
src/include/executor/executor.h | 2 +
src/include/nodes/makefuncs.h | 10 +
src/include/nodes/nodes.h | 24 +
src/include/nodes/parsenodes.h | 203 ++
src/include/nodes/primnodes.h | 222 ++
src/include/parser/kwlist.h | 22 +
src/include/utils/formatting.h | 4 +
src/include/utils/json.h | 26 +
src/include/utils/jsonb.h | 47 +
src/include/utils/jsonfuncs.h | 7 +
src/include/utils/jsonpath.h | 33 +
src/interfaces/ecpg/preproc/parse.pl | 2 +
src/interfaces/ecpg/preproc/parser.c | 14 +
src/test/regress/expected/json_sqljson.out | 15 +
src/test/regress/expected/jsonb.out | 130 ++
src/test/regress/expected/jsonb_sqljson.out | 1018 +++++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/expected/sqljson.out | 1352 +++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/json_sqljson.sql | 11 +
src/test/regress/sql/jsonb.sql | 38 +
src/test/regress/sql/jsonb_sqljson.sql | 317 +++
src/test/regress/sql/opr_sanity.sql | 6 +-
src/test/regress/sql/sqljson.sql | 467 ++++
59 files changed, 13151 insertions(+), 300 deletions(-)
create mode 100644 src/test/regress/expected/json_sqljson.out
create mode 100644 src/test/regress/expected/jsonb_sqljson.out
create mode 100644 src/test/regress/expected/sqljson.out
create mode 100644 src/test/regress/sql/json_sqljson.sql
create mode 100644 src/test/regress/sql/jsonb_sqljson.sql
create mode 100644 src/test/regress/sql/sqljson.sql
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index ef0e2a7746..1b9a552ec8 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9500,6 +9500,25 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
</listitem>
</varlistentry>
+ <varlistentry id="guc-sql-json" xreflabel="sql_json">
+ <term><varname>sql_json</varname> (<type>enum</type>)
+ <indexterm><primary>json</primary></indexterm>
+ <indexterm><primary>jsonb</primary></indexterm>
+ <indexterm>
+ <primary><varname>sql_json</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Valid values are <literal>json</literal> and <literal>jsonb</literal>.
+ Specifies what <productname>PostgreSQL</productname> type is used
+ as an implementation of SQL type <type>JSON</type>.
+ When <varname>sql_json</varname> is set to <literal>jsonb</literal>,
+ <productname>PostgreSQL</productname> type <type>json</type> can be
+ accessed using explicit qualification <type>pg_catalog.json</type>.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
</sect1>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 78812b2dbe..a75b92bdb9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17456,6 +17456,1924 @@ $.* ? (@ like_regex "^\\d+$")
</para>
</sect3>
</sect2>
+
+ <sect2 id="functions-sqljson">
+ <title>SQL/JSON Functions and Expressions</title>
+ <indexterm zone="functions-json">
+ <primary>SQL/JSON</primary>
+ <secondary>functions and expressions</secondary>
+ </indexterm>
+
+ <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.
+ </para>
+
+ <para>
+ SQL/JSON enables you to handle JSON data alongside regular SQL data,
+ with transaction support:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Upload JSON data into a relational database and store it in
+ regular SQL columns as character or binary strings.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Generate JSON objects and arrays from relational data.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Query JSON data using SQL/JSON query functions and SQL/JSON path
+ language expressions.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ All SQL/JSON functions fall into one of two groups.
+ <link linkend="functions-sqljson-producing">Constructor functions</link>
+ generate JSON data from values of SQL types.
+ <link linkend="functions-sqljson-querying">Query functions</link>
+ evaluate SQL/JSON path language expressions against JSON values
+ and produce values of SQL/JSON types, which are converted to SQL types.
+ </para>
+
+ <sect3 id="functions-sqljson-producing">
+ <title>Producing JSON Content</title>
+
+ <para>
+ <productname>PostgreSQL</productname> provides several functions
+ that generate JSON data. Taking values of SQL types as input, these
+ functions construct JSON objects, JSON arrays or JSON scalars represented
+ as <type>json</type> or <type>jsonb</type> types, SQL character or binary strings.
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <link linkend="functions-jsonparse"><literal>JSON</literal></link>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <link linkend="functions-jsonscalar"><literal>JSON_SCALAR</literal></link>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <link linkend="functions-jsonobject"><literal>JSON_OBJECT</literal></link>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <link linkend="functions-jsonobjectagg"><literal>JSON_OBJECTAGG</literal></link>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <link linkend="functions-jsonarray"><literal>JSON_ARRAY</literal></link>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <link linkend="functions-jsonarrayagg"><literal>JSON_ARRAYAGG</literal></link>
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <sect4 id="functions-jsonparse">
+ <title><literal>JSON</literal></title>
+ <indexterm><primary>json</primary></indexterm>
+<synopsis>
+<function>JSON</function> (
+ <parameter>expression</parameter> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional>
+ <optional> { WITH | WITHOUT } UNIQUE <optional> KEYS </optional> </optional>
+ <optional> RETURNING <replaceable class="parameter">json_data_type</replaceable> </optional>
+)
+</synopsis>
+
+ <sect5>
+ <title>Description</title>
+
+ <para>
+ <function>JSON</function> function generates a <acronym>JSON</acronym>
+ from a text data.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Parameters</title>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal><parameter>expression</parameter> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional></literal>
+ </term>
+ <listitem>
+ <para>
+ String expression that provides the <acronym>JSON</acronym> text data.
+ Accepted any character strings (<type>text</type>, <type>char</type>, etc.)
+ or binary strings (<type>bytea</type>) in UTF8 encoding.
+ For null input, <acronym>SQL</acronym> null value is returned.
+ </para>
+ <para>
+ The optional <literal>FORMAT</literal> clause is provided to conform
+ to the SQL/JSON standard.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <literal>{ WITH | WITHOUT } UNIQUE <optional> KEYS </optional></literal>
+ </term>
+ <listitem>
+ <para>
+ Defines whether duplicate keys are allowed:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><literal>WITHOUT</literal></term>
+ <listitem>
+ <para>
+ Default. The constructed
+ <acronym>JSON</acronym> object can contain duplicate keys.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>WITH</literal></term>
+ <listitem>
+ <para>
+ Duplicate keys are not allowed.
+ If the input data contains duplicate keys, an error is returned.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ Optionally, you can add the <literal>KEYS</literal> keyword for
+ semantic clarity.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">json_data_type</replaceable></literal>
+ </term>
+ <listitem>
+ <para>
+ The output clause that specifies the type (<type>json</type> or
+ <type>jsonb</type>) of the generated <acronym>JSON</acronym>.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect5>
+
+ <sect5>
+ <title>Notes</title>
+ <para>
+ Alternatively, you can construct <acronym>JSON</acronym> values simply
+ using <productname>PostgreSQL</productname>-specific casts to
+ <type>json</type> and <type>jsonb</type> types.
+ </para>
+ </sect5>
+ <sect5>
+ <title>Examples</title>
+ <para>
+ Construct a JSON the provided strings:
+ </para>
+<screen>
+SELECT JSON('{ "a" : 123, "b": [ true, "foo" ], "a" : "bar" }');
+ json
+--------------------------------------------------
+ { "a" : 123, "b": [ true, "foo" ], "a" : "bar" }
+(1 row)
+
+
+SELECT JSON('{"a": 123, "b": [true, "foo"], "a": "bar"}' RETURNING jsonb);
+ json
+----------------------------------
+ {"a": "bar", "b": [true, "foo"]}
+(1 row)
+
+SELECT JSON('{"a": 123, "b": [true, "foo"], "a": "bar"}' WITH UNIQUE KEYS);
+ERROR: duplicate JSON object key value
+</screen>
+ </sect5>
+ </sect4>
+
+ <sect4 id="functions-jsonscalar">
+ <title><literal>JSON_SCALAR</literal></title>
+ <indexterm><primary>json_scalar</primary></indexterm>
+
+<synopsis>
+<function>JSON_SCALAR</function> (
+ <parameter>expression</parameter>
+ <optional> RETURNING <replaceable class="parameter">json_data_type</replaceable> </optional>
+)
+</synopsis>
+
+ <sect5>
+ <title>Description</title>
+
+ <para>
+ <function>JSON_SCALAR</function> function generates a scalar
+ <acronym>JSON</acronym> from a <acronym>SQL</acronym> data.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Parameters</title>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal><parameter>expression</parameter></literal>
+ </term>
+ <listitem>
+ <para>
+ Expression that provides the data for constructing a
+ <acronym>JSON</acronym>.
+ For null input, <acronym>SQL</acronym> null
+ (not a <acronym>JSON</acronym> null) value is returned.
+ For any scalar other than a number, a Boolean, the text representation
+ will be used, with escaping as necessary to make it a valid
+ <acronym>JSON</acronym> string value.
+ For details, see
+ <function>to_json()</function>/<function>to_jsonb()</function>
+ in <xref linkend="functions-json-creation-table"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">json_data_type</replaceable></literal>
+ </term>
+ <listitem>
+ <para>
+ The output clause that specifies the type (<type>json</type> or
+ <type>jsonb</type>) of the generated <acronym>JSON</acronym> scalar.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect5>
+
+ <sect5>
+ <title>Notes</title>
+ <para>
+ Alternatively, you can construct <acronym>JSON</acronym> objects by
+ using <productname>PostgreSQL</productname>-specific
+ <function>to_json()</function>/<function>to_jsonb()</function> functions.
+ See <xref linkend="functions-json-creation-table"/> for details.
+ </para>
+ </sect5>
+ <sect5>
+ <title>Examples</title>
+ <para>
+ Construct a JSON from the provided values various types:
+ </para>
+<screen>
+SELECT JSON_SCALAR(123.45);
+ json_scalar
+-------------
+ 123.45
+(1 row)
+
+SELECT JSON_SCALAR('123');
+ json_scalar
+-------------
+ "123"
+(1 row)
+
+SELECT JSON_SCALAR(true);
+ json_scalar
+-------------
+ true
+(1 row)
+</screen>
+ </sect5>
+ </sect4>
+
+ <sect4 id="functions-jsonobject">
+ <title><literal>JSON_OBJECT</literal></title>
+ <indexterm><primary>json_object</primary></indexterm>
+
+<synopsis>
+<function>JSON_OBJECT</function> (
+ <optional> { <parameter>key_expression</parameter> { VALUE | ':' }
+ <parameter>value_expression</parameter> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional> }<optional>, ...</optional> </optional>
+ <optional> { NULL | ABSENT } ON NULL </optional>
+ <optional> { WITH | WITHOUT } UNIQUE <optional> KEYS </optional> </optional>
+ <optional> RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional> </optional>
+)
+</synopsis>
+
+ <sect5>
+ <title>Description</title>
+
+ <para>
+ The <function>JSON_OBJECT</function> function generates a <acronym>JSON</acronym>
+ object from <acronym>SQL</acronym> or <acronym>JSON</acronym> data.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Parameters</title>
+ <variablelist>
+
+ <varlistentry>
+ <term>
+ <literal>
+ <parameter>key_expression</parameter> { VALUE | ':' }
+ <parameter>value_expression</parameter> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional>
+ </literal>
+ </term>
+ <listitem>
+ <para>
+ The input clause that provides the data for constructing a <acronym>JSON</acronym> object:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <parameter>key_expression</parameter> is a scalar expression
+ defining the <acronym>JSON</acronym> key, which is implicitly
+ converted to the <type>text</type> type.
+ The provided expression cannot be <literal>NULL</literal> or
+ belong to a type that has a cast to <type>json</type>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <parameter>value_expression</parameter> is an expression
+ that provides the input for the <acronym>JSON</acronym> value.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The optional <literal>FORMAT</literal> clause is provided to
+ conform to the SQL/JSON standard.
+ </para>
+ </listitem>
+ </itemizedlist>
+ <para>
+ You must use a colon or the <literal>VALUE</literal> keyword as a
+ delimiter between the key and the value. Multiple key/value pairs are
+ separated by commas.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ NULL | ABSENT } ON NULL</literal>
+ </term>
+ <listitem>
+ <para>
+ Defines whether <literal>NULL</literal> values are allowed in the constructed
+ <acronym>JSON</acronym> object:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><literal>NULL</literal></term>
+ <listitem>
+ <para>
+ Default. <literal>NULL</literal> values are allowed.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>ABSENT</literal></term>
+ <listitem>
+ <para>
+ If the value is <literal>NULL</literal>,
+ the corresponding key/value pair is omitted from the generated
+ <acronym>JSON</acronym> object.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ WITH | WITHOUT } UNIQUE <optional> KEYS </optional></literal>
+ </term>
+ <listitem>
+ <para>Defines whether duplicate keys are allowed:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><literal>WITHOUT</literal></term>
+ <listitem>
+ <para>
+ Default. The constructed
+ <acronym>JSON</acronym> object can contain duplicate keys.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>WITH</literal></term>
+ <listitem>
+ <para>
+ Duplicate keys are not allowed.
+ If the input data contains duplicate keys, an error is returned.
+ This check is performed before removing JSON items with NULL values.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ Optionally, you can add the <literal>KEYS</literal> keyword for semantic clarity.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional></literal>
+ </term>
+ <listitem>
+ <para>
+ The output clause that specifies the type of the generated <acronym>JSON</acronym> object.
+ For details, see <xref linkend="sqljson-output-clause"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect5>
+
+ <sect5>
+ <title>Notes</title>
+ <para>Alternatively, you can construct <acronym>JSON</acronym> objects by using
+ <productname>PostgreSQL</productname>-specific <function>json_build_object()</function>/
+ <function>jsonb_build_object()</function> functions.
+ See <xref linkend="functions-json-creation-table"/> for details.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Examples</title>
+ <para>
+ Construct a JSON object from the provided key/value pairs of various types:
+ </para>
+<screen>
+SELECT JSON_OBJECT(
+-- scalar JSON types
+ 'key1': 'string',
+ 'key2': '[1, 2]',
+ 'key3' VALUE 123, -- alternative syntax for key-value delimiter
+ 'key4': NULL,
+-- other types
+ 'key5': ARRAY[1, 2, 3], -- postgres array
+ 'key6': jsonb '{"a": ["b", 1]}', -- composite json/jsonb
+ 'key7': date '2017-09-30', -- datetime type
+ 'key8': row(1, 'a'), -- row type
+ 'key9': '[1, 2]' FORMAT JSON, -- same value as for key2, but with FORMAT
+-- key can be an expression
+ 'key' || 'last' : TRUE
+ABSENT ON NULL) AS json;
+ json
+----------------------------------------------------
+{"key1" : "string", "key2" : "[1, 2]", "key3" : 123,
+ "key5" : [1,2,3], "key6" : {"a": ["b", 1]},
+ "key7" : "2017-09-30", "key8" : {"f1":1,"f2":"a"},
+ "key9" : [1, 2], "keylast" : true}
+(1 row)
+</screen>
+
+ <para>
+ From the <structname>films</structname> table, select some data
+ about the films distributed by Paramount Pictures
+ (<literal>did</literal> = 103) and return JSON objects:
+ </para>
+<screen>
+SELECT
+JSON_OBJECT(
+ 'code' VALUE f.code,
+ 'title' VALUE f.title,
+ 'did' VALUE f.did
+) AS paramount
+FROM films AS f
+WHERE f.did = 103;
+ paramount
+----------------------------------------------------
+{"code" : "P_301", "title" : "Vertigo", "did" : 103}
+{"code" : "P_302", "title" : "Becket", "did" : 103}
+{"code" : "P_303", "title" : "48 Hrs", "did" : 103}
+(3 rows)
+</screen>
+ </sect5>
+ </sect4>
+
+ <sect4 id="functions-jsonobjectagg">
+ <title><literal>JSON_OBJECTAGG</literal></title>
+ <indexterm><primary>json_objectagg</primary></indexterm>
+
+<synopsis>
+<function>JSON_OBJECTAGG</function> (
+ <optional> { <parameter>key_expression</parameter> { VALUE | ':' } <parameter>value_expression</parameter> } </optional>
+ <optional> { NULL | ABSENT } ON NULL </optional>
+ <optional> { WITH | WITHOUT } UNIQUE <optional> KEYS </optional> </optional>
+ <optional> RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional> </optional>
+)
+
+</synopsis>
+
+ <sect5>
+ <title>Description</title>
+
+ <para>
+ The <function>JSON_OBJECTAGG</function> function aggregates the provided data
+ into a <acronym>JSON</acronym> object. You can use this function to combine values
+ stored in different table columns into pairs. If you specify a <command>GROUP BY</command>
+ or an <command>ORDER BY</command> clause, this function returns a separate JSON object
+ for each table row.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal><parameter>key_expression</parameter> { VALUE | ':' } <parameter>value_expression</parameter></literal>
+ </term>
+ <listitem>
+
+ <para>
+ The input clause that provides the data to be aggregated as a <acronym>JSON</acronym> object:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <parameter>key_expression</parameter> is a scalar expression
+ defining the <acronym>JSON</acronym> key, which is implicitly
+ converted to the <type>text</type> type.
+ The provided expression cannot be <literal>NULL</literal> or
+ belong to a type that has a cast to <type>json</type>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <parameter>value_expression</parameter> is an expression that
+ provides the input for the <acronym>JSON</acronym> value preceded
+ by its type.
+ For <acronym>JSON</acronym> scalar types, you can omit the type.
+ </para>
+ <note>
+ <para>
+ The input value of the <literal>bytea</literal> type must be stored in <literal>UTF8</literal>
+ and contain a valid <literal>UTF8</literal> string. Otherwise, an error occurs.
+ <productname>PostgreSQL</productname> currently supports only <literal>UTF8</literal>.
+ </para>
+ </note>
+ </listitem>
+ </itemizedlist>
+ <para>
+ You must use a colon or the <literal>VALUE</literal> keyword as a delimiter between
+ keys and values. Multiple key/value pairs are separated by commas.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ NULL | ABSENT } ON NULL</literal>
+ </term>
+ <listitem>
+ <para>
+ Defines whether <literal>NULL</literal> values are allowed in the constructed
+ <acronym>JSON</acronym> object:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><literal>NULL</literal></term>
+ <listitem>
+ <para>
+ Default. <literal>NULL</literal> values are allowed.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>ABSENT</literal></term>
+ <listitem>
+ <para>
+ If the value is <literal>NULL</literal>,
+ the corresponding key/value pair is omitted from the generated
+ <acronym>JSON</acronym> object.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ WITH | WITHOUT } UNIQUE <optional> KEYS </optional></literal>
+ </term>
+ <listitem>
+ <para>Defines whether duplicate keys are allowed:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><literal>WITHOUT</literal></term>
+ <listitem>
+ <para>
+ Default. The constructed
+ <acronym>JSON</acronym> object can contain duplicate keys.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>WITH</literal></term>
+ <listitem>
+ <para>
+ Duplicate keys are not allowed.
+ If the input data contains duplicate keys, an error is returned.
+ This check is performed before removing JSON items with NULL values.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ Optionally, you can add the <literal>KEYS</literal> keyword for semantic clarity.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional></literal>
+ </term>
+ <listitem>
+ <para>
+ The output clause that specifies the type of the generated <acronym>JSON</acronym> object.
+ For details, see <xref linkend="sqljson-output-clause"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect5>
+
+ <sect5>
+ <title>Notes</title>
+ <para>Alternatively, you can create <acronym>JSON</acronym> objects by using
+ <productname>PostgreSQL</productname>-specific <function>json_object_agg()</function>/
+ <function>jsonb_object_agg()</function> aggregate functions.
+ See <xref linkend="functions-aggregate"/> for details.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Examples</title>
+
+ <para>
+ For films with <literal>did</literal> = 103, aggregate key/value pairs
+ of film genre (<literal>f.kind</literal>) and title (<literal>f.title</literal>)
+ into a single object:
+ </para>
+<screen>
+SELECT
+JSON_OBJECTAGG(
+ f.kind VALUE f.title)
+ AS films_list
+FROM films AS f
+where f.did = 103;
+ films_list
+----------------------------------------------------
+{ "Action" : "Vertigo", "Drama" : "Becket", "Action" : "48 Hrs" }
+</screen>
+
+ <para>
+ Return the same object as <type>jsonb</type>. Note that only a single film of
+ the action genre is included as the <type>jsonb</type> type does not allow duplicate keys.
+ </para>
+<screen>
+SELECT
+JSON_OBJECTAGG(
+ f.kind VALUE f.title
+ RETURNING jsonb)
+AS films_list
+FROM films AS f
+where f.did = 103;
+ films_list
+----------------------------------------------------
+{"Drama": "Becket", "Action": "48 Hrs"}
+</screen>
+
+ <para>
+ Return objects of film titles and length, grouped by the film genre:
+ </para>
+<screen>
+SELECT
+ f.kind,
+ JSON_OBJECTAGG(
+ f.title VALUE f.len
+) AS films_list
+FROM films AS f
+GROUP BY f.kind;
+
+ kind | films_list
+-------------+----------------------------------
+Musical | { "West Side Story" : "02:32:00", "The King and I" : "02:13:00", "Bed Knobs and Broomsticks" : "01:57:00" }
+Romantic | { "The African Queen" : "01:43:00", "Une Femme est une Femme" : "01:25:00", "Storia di una donna" : "01:30:00" }
+Comedy | { "Bananas" : "01:22:00", "There's a Girl in my Soup" : "01:36:00" }
+Drama | { "The Third Man" : "01:44:00", "Becket" : "02:28:00", "War and Peace" : "05:57:00", "Yojimbo" : "01:50:00", "Das Boot" : "02:29:00" }
+Action | { "Vertigo" : "02:08:00", "48 Hrs" : "01:37:00", "Taxi Driver" : "01:54:00", "Absence of Malice" : "01:55:00" }
+(5 rows)
+</screen>
+ </sect5>
+ </sect4>
+
+ <sect4 id="functions-jsonarray">
+ <title><literal>JSON_ARRAY</literal></title>
+ <indexterm><primary>json_array</primary></indexterm>
+
+<synopsis>
+<function>JSON_ARRAY</function> (
+ <optional> { <parameter>value_expression</parameter> <optional> FORMAT JSON </optional> } <optional>, ...</optional> </optional>
+ <optional> { NULL | ABSENT } ON NULL </optional>
+ <optional> RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional> </optional>
+)
+</synopsis>
+<synopsis>JSON_ARRAY (
+ <optional> <replaceable class="parameter">query_expression</replaceable> </optional>
+ <optional> RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional> </optional>
+)
+</synopsis>
+
+ <sect5>
+ <title>Description</title>
+
+ <para>
+ The <function>JSON_ARRAY</function> function constructs a <acronym>JSON</acronym> array from
+ the provided <acronym>SQL</acronym> or <acronym>JSON</acronym> data.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal><parameter>value_expression</parameter></literal>
+ </term>
+ <listitem>
+
+ <para>
+ The input clause that provides the data for constructing a JSON array.
+ The <replaceable class="parameter">value_expression</replaceable> is an expression
+ that provides the input for the <acronym>JSON</acronym> value preceded by its type.
+ For <acronym>JSON</acronym> scalar types, you can omit the type.
+ </para>
+ <note>
+ <para>
+ The input value of the <literal>bytea</literal> type must be stored in <literal>UTF8</literal>
+ and contain a valid <literal>UTF8</literal> string. Otherwise, an error occurs.
+ <productname>PostgreSQL</productname> currently supports only <literal>UTF8</literal>.
+ </para>
+ </note>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal><replaceable class="parameter">query_expression</replaceable></literal>
+ </term>
+ <listitem>
+ <para>
+ An SQL query that provides the data for constructing a JSON array.
+ The query must return a single column that holds the values to be
+ used in the array.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ NULL | ABSENT } ON NULL</literal>
+ </term>
+ <listitem>
+ <para>
+ Defines whether <literal>NULL</literal> values are allowed in the generated <acronym>JSON</acronym> array:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><literal>NULL</literal></term>
+ <listitem>
+ <para>
+ <literal>NULL</literal> values are allowed.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>ABSENT</literal></term>
+ <listitem>
+ <para>
+ Default. If the value is <literal>NULL</literal>,
+ the corresponding key/value pair is omitted from the generated
+ <acronym>JSON</acronym> object.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ This clause is only supported for arrays built from an explicit list of values.
+ If you are using an SQL query to generate an array, NULL values are always
+ omitted.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional></literal>
+ </term>
+ <listitem>
+ <para>
+ The output clause that specifies the return type of the constructed <acronym>JSON</acronym> array.
+ For details, see <xref linkend="sqljson-output-clause"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+</variablelist>
+ </sect5>
+
+ <sect5>
+ <title>Notes</title>
+ <para>Alternatively, you can create <acronym>JSON</acronym> arrays by using
+ <productname>PostgreSQL</productname>-specific <function>json_build_array()</function>/
+ <function>jsonb_build_array()</function> functions.
+ See <xref linkend="functions-json"/> for details.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Examples</title>
+ <para>
+ From the <structname>films</structname> table, select some data
+ about the films distributed by Paramount Pictures
+ (<literal>did</literal> = 103) and return JSON arrays:
+ </para>
+<screen>
+SELECT
+JSON_ARRAY(
+ f.code,
+ f.title,
+ f.did
+) AS films
+FROM films AS f
+WHERE f.did = 103;
+ films
+----------------------------------------------------
+["code" : "P_301", "title" : "Vertigo", "did" : 103]
+["code" : "P_302", "title" : "Becket", "did" : 103]
+["code" : "P_303", "title" : "48 Hrs", "did" : 103]
+(3 rows)
+</screen>
+ <para>
+ Construct a JSON array from the list of film titles returned from the
+ <structname>films</structname> table by a subquery:
+ </para>
+<screen>
+SELECT
+JSON_ARRAY(
+ SELECT
+ f.title
+FROM films AS f
+where f.did = 103)
+AS film_titles;
+ film_titles
+----------------------------------------------------
+["Vertigo", "Becket", "48 Hrs"]
+(1 row)
+</screen>
+ </sect5>
+ </sect4>
+
+ <sect4 id="functions-jsonarrayagg">
+ <title><literal>JSON_ARRAYAGG</literal></title>
+ <indexterm><primary>json_arrayagg</primary></indexterm>
+
+<synopsis>
+<function>JSON_ARRAYAGG</function> (
+ <optional> <parameter>value_expression</parameter> </optional>
+ <optional> ORDER BY <replaceable class="parameter">sort_expression</replaceable> </optional>
+ <optional> { NULL | ABSENT } ON NULL </optional>
+ <optional> RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional> </optional>
+)
+
+</synopsis>
+
+ <sect5>
+ <title>Description</title>
+
+ <para>
+ The <function>JSON_ARRAYAGG</function> function aggregates the provided <acronym>SQL</acronym>
+ or <acronym>JSON</acronym> data into a <acronym>JSON</acronym> array.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal><parameter>value_expression</parameter></literal>
+ </term>
+ <listitem>
+
+ <para>
+ The input clause that provides the input data to be aggregated as
+ a <acronym>JSON</acronym> array.
+ The <parameter>value_expression</parameter> can be a value or a query
+ returning the values to be used as input in array construction.
+ You can provide multiple input values separated by commas.
+ </para>
+ </listitem>
+ </varlistentry>
+
+<varlistentry>
+ <term>
+ <literal>ORDER BY</literal>
+ </term>
+ <listitem>
+ <para>
+ Sorts the input data to be aggregated as a <acronym>JSON</acronym> array.
+ For details on the exact syntax of the <literal>ORDER BY</literal> clause, see <xref linkend="sql-orderby"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ NULL | ABSENT } ON NULL</literal>
+ </term>
+ <listitem>
+ <para>
+ Defines whether <literal>NULL</literal> values are allowed in the constructed array:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>NULL</literal> — <literal>NULL</literal> values are allowed.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>ABSENT</literal> (default) — <literal>NULL</literal>
+ values are omitted from the generated array.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional></literal>
+ </term>
+ <listitem>
+ <para>
+ The output clause that specifies the return type of the constructed <acronym>JSON</acronym> array.
+ For details, see <xref linkend="sqljson-output-clause"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+</variablelist>
+ </sect5>
+
+<sect5>
+ <title>Notes</title>
+ <para>Alternatively, you can create <acronym>JSON</acronym> arrays by using
+ <productname>PostgreSQL</productname>-specific <function>json_agg()</function>/
+ <function>jsonb_agg()</function> functions.
+ See <xref linkend="functions-aggregate"/> for details.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Examples</title>
+ <para>
+ Construct an array of film titles sorted in alphabetical order:
+ </para>
+<screen>
+SELECT
+JSON_ARRAYAGG(
+ f.title
+ORDER BY f.title ASC) AS film_titles
+FROM films AS f;
+ film_titles
+----------------------------------------------------
+["48 Hrs", "Absence of Malice", "Bananas", "Becket", "Bed Knobs and Broomsticks", "Das Boot", "Storia di una donna", "Taxi Driver", "The African Queen", "The King and I", "There's a Girl in my Soup", "The Third Man", "Une Femme est une Femme", "Vertigo", "War and Peace", "West Side Story", "Yojimbo"]
+(1 row)
+</screen>
+ </sect5>
+ </sect4>
+ </sect3>
+
+ <sect3 id="functions-sqljson-querying">
+ <title>Querying JSON</title>
+
+ <para>
+ SQL/JSON query functions evaluate SQL/JSON path language expressions
+ against JSON values, producing values of SQL/JSON types, which are
+ converted to SQL types. All SQL/JSON query functions accept several
+ common clauses described in <xref linkend="sqljson-common-clauses"/>.
+ For details on the SQL/JSON path language,
+ see <xref linkend="functions-sqljson-path"/>.
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <link linkend="functions-isjson-predicate"><literal>IS JSON</literal></link>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <link linkend="functions-jsonexists"><literal>JSON_EXISTS</literal></link>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <link linkend="functions-jsonvalue"><literal>JSON_VALUE</literal></link>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <link linkend="functions-jsonquery"><literal>JSON_QUERY</literal></link>
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ In some usage examples for these functions,
+ the following small table storing some JSON data will be used:
+<programlisting>
+CREATE TABLE my_films (
+ js text );
+
+INSERT INTO my_films VALUES (
+'{ "favorites" : [
+ { "kind" : "comedy", "films" : [
+ { "title" : "Bananas",
+ "director" : "Woody Allen"},
+ { "title" : "The Dinner Game",
+ "director" : "Francis Veber" } ] },
+ { "kind" : "horror", "films" : [
+ { "title" : "Psycho",
+ "director" : "Alfred Hitchcock" } ] },
+ { "kind" : "thriller", "films" : [
+ { "title" : "Vertigo",
+ "director" : "Alfred Hitchcock" } ] },
+ { "kind" : "drama", "films" : [
+ { "title" : "Yojimbo",
+ "director" : "Akira Kurosawa" } ] }
+ ] }');
+</programlisting>
+ </para>
+
+ <sect4 id="functions-jsonexists">
+ <title><literal>JSON_EXISTS</literal></title>
+ <indexterm><primary>json_exists</primary></indexterm>
+
+<synopsis>
+<function>JSON_EXISTS</function> (
+ <replaceable>context_item</replaceable>, <replaceable>path_expression</replaceable> <optional> PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } <optional>, ...</optional></optional>
+ <optional> RETURNING <replaceable class="parameter">data_type</replaceable> </optional>
+ <optional> { TRUE | FALSE | UNKNOWN | ERROR } ON ERROR </optional>
+)
+</synopsis>
+
+ <sect5>
+ <title>Description</title>
+
+ <para>
+ <function>JSON_EXISTS</function> function checks whether the provided
+ <acronym>JSON</acronym> path expression can return any <acronym>SQL/JSON</acronym> items.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Parameters</title>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal><replaceable>context_item</replaceable>, <replaceable>path_expression</replaceable> <optional> PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } <optional>, ...</optional></optional></literal>
+ </term>
+ <listitem>
+
+ <para>
+ The input data to query, the JSON path expression defining the query, and an optional <literal>PASSING</literal> clause.
+ See <xref linkend="sqljson-input-clause"/> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable></literal>
+ </term>
+ <listitem>
+ <para>
+ The output clause that specifies the data type of the returned value.
+ The specified data type should have a cast from a <literal>boolean</literal>
+ type, which is returned by default.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ TRUE | FALSE | UNKNOWN | ERROR } ON ERROR</literal>
+ </term>
+ <listitem>
+ <para>
+ Defines the return value if an error occurs. The default value is <literal>FALSE</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect5>
+
+ <sect5>
+ <title>Examples</title>
+
+ <para>
+ Check whether the provided <type>jsonb</type> data contains a
+ key/value pair with the <literal>key1</literal> key, and its value
+ contains an array with one or more elements bigger than 2:
+ </para>
+<screen>
+SELECT JSON_EXISTS(jsonb '{"key1": [1,2,3]}', 'strict $.key1[*] ? (@ > 2)');
+ json_exists
+-------------
+ t
+(1 row)
+</screen>
+
+ <para>
+ Note the difference between strict and lax modes
+ if the required item does not exist:
+ </para>
+<screen>
+-- Strict mode with ERROR on ERROR clause
+SELECT JSON_EXISTS(jsonb '{"a": [1,2,3]}', 'strict $.a[5]' ERROR ON ERROR);
+ERROR: Invalid SQL/JSON subscript
+(1 row)
+</screen>
+
+<screen>
+-- Lax mode
+SELECT JSON_EXISTS(jsonb '{"a": [1,2,3]}', 'lax $.a[5]' ERROR ON ERROR);
+ json_exists
+-------------
+ f
+(1 row)
+</screen>
+
+<screen>
+-- Strict mode using the default value for the ON ERROR clause
+SELECT JSON_EXISTS(jsonb '{"a": [1,2,3]}', 'strict $.a[5]');
+ json_exists
+-------------
+ f
+(1 row)
+</screen>
+
+ </sect5>
+ </sect4>
+
+ <sect4 id="functions-jsonvalue">
+ <title><literal>JSON_VALUE</literal></title>
+ <indexterm><primary>json_value</primary></indexterm>
+
+<synopsis>
+<function>JSON_VALUE</function> (
+ <replaceable>context_item</replaceable>, <replaceable>path_expression</replaceable> <optional> PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } <optional>, ...</optional></optional>
+ <optional> RETURNING <replaceable class="parameter">data_type</replaceable> </optional>
+ <optional> { ERROR | NULL | DEFAULT <replaceable class="parameter">expression</replaceable> } ON EMPTY </optional>
+ <optional> { ERROR | NULL | DEFAULT <replaceable class="parameter">expression</replaceable> } ON ERROR </optional>
+)
+ </synopsis>
+
+ <sect5>
+ <title>Description</title>
+
+ <para>
+ <function>JSON_VALUE</function> function extracts a value from the provided
+ <acronym>JSON</acronym> data and converts it to an <acronym>SQL</acronym> scalar.
+ If the specified JSON path expression returns more than one
+ <acronym>SQL/JSON</acronym> item, an error occurs. To extract
+ an <acronym>SQL/JSON</acronym> array or object, use <xref linkend="functions-jsonquery"/>.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Parameters</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>
+ <literal><replaceable>context_item</replaceable>, <replaceable>path_expression</replaceable> <optional> PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } <optional>, ...</optional></optional></literal>
+ </term>
+ <listitem>
+
+ <para>
+ The input data to query, the JSON path expression defining the query, and an optional <literal>PASSING</literal> clause.
+ For details, see <xref linkend="functions-sqljson-path"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable></literal>
+ </term>
+ <listitem>
+ <para>
+ The output clause that specifies the data type of the returned value.
+ Out of the box, <productname>PostgreSQL</productname>
+ supports the following types: <literal>json</literal>, <literal>jsonb</literal>,
+ <literal>bytea</literal>, and character string types (<literal>text</literal>, <literal>char</literal>,
+ <literal>varchar</literal>, and <literal>nchar</literal>).
+ The extracted value must be a single <acronym>SQL/JSON</acronym> scalar item
+ and have a cast to the specified type. Otherwise, an error occurs.
+ By default, <function>JSON_VALUE</function> returns a string
+ of the <literal>text</literal> type.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ ERROR | NULL | DEFAULT <replaceable class="parameter">expression</replaceable> } ON EMPTY</literal>
+ </term>
+ <listitem>
+ <para>
+ Defines the return value if no JSON value is found. The default is
+ <literal>NULL</literal>. If you use
+ <literal>DEFAULT <replaceable class="parameter">expression</replaceable></literal>,
+ the provided <replaceable class="parameter">expression</replaceable> is
+ evaluated and cast to the type specified in the <command>RETURNING</command> clause.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ ERROR | NULL | DEFAULT <replaceable class="parameter">expression</replaceable> } ON ERROR</literal>
+ </term>
+ <listitem>
+ <para>
+ Defines the return value if an unhandled error occurs. The default is
+ <literal>NULL</literal>. If you use
+ <literal>DEFAULT <replaceable class="parameter">expression</replaceable></literal>,
+ the provided <replaceable class="parameter">expression</replaceable> is
+ evaluated and cast to the type specified in the <command>RETURNING</command> clause.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect5>
+
+ <sect5>
+ <title>Examples</title>
+
+ <para>
+ Extract an SQL/JSON value and return it as an SQL
+ scalar of the specified type. Note that
+ <command>JSON_VALUE</command> can only return a
+ single scalar, and the returned value must have a
+ cast to the specified return type:
+ </para>
+
+<screen>
+SELECT JSON_VALUE('"123.45"', '$' RETURNING float);
+ json_value
+------------
+ 123.45
+(1 row)
+
+SELECT JSON_VALUE('123.45', '$' RETURNING int ERROR ON ERROR);
+ json_value
+------------
+ 123
+(1 row)
+
+SELECT JSON_VALUE('"03:04 2015-02-01"', '$.datetime("HH24:MI YYYY-MM-DD")' RETURNING date);
+ json_value
+------------
+ 2015-02-01
+(1 row)
+
+SELECT JSON_VALUE('"123.45"', '$' RETURNING int ERROR ON ERROR);
+ERROR: invalid input syntax for integer: "123.45"
+
+SELECT JSON_VALUE(jsonb '[1]', 'strict $' ERROR ON ERROR);
+ERROR: SQL/JSON scalar required
+
+SELECT JSON_VALUE(jsonb '[1,2]', 'strict $[*]' ERROR ON ERROR);
+ERROR: more than one SQL/JSON item
+</screen>
+
+ <para>
+ If the path expression returns an array, an object, or
+ multiple SQL/JSON items, an error is returned, as specified
+ in the <command>ON ERROR</command> clause:
+ </para>
+<screen>
+SELECT JSON_VALUE(jsonb '[1]', 'strict $' ERROR ON ERROR);
+ERROR: SQL/JSON scalar required
+
+SELECT JSON_VALUE(jsonb '{"a": 1}', 'strict $' ERROR ON ERROR);
+ERROR: SQL/JSON scalar required
+
+SELECT JSON_VALUE(jsonb '[1,2]', 'strict $[*]' ERROR ON ERROR);
+ERROR: more than one SQL/JSON item
+
+SELECT JSON_VALUE(jsonb '[1,2]', 'strict $[*]' DEFAULT 1 ON ERROR);
+1
+</screen>
+
+ </sect5>
+ </sect4>
+
+ <sect4 id="functions-jsonquery">
+ <title><literal>JSON_QUERY</literal></title>
+ <indexterm><primary>json_query</primary></indexterm>
+
+<synopsis>
+<function>JSON_QUERY</function> (
+ <replaceable>context_item</replaceable>, <replaceable>path_expression</replaceable> <optional> PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } <optional>, ...</optional></optional>
+ <optional> RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional> </optional>
+ <optional> { WITHOUT | WITH { CONDITIONAL | <optional>UNCONDITIONAL</optional> } } <optional> ARRAY </optional> WRAPPER </optional>
+ <optional> { KEEP | OMIT } QUOTES <optional> ON SCALAR STRING </optional> </optional>
+ <optional> { ERROR | NULL | EMPTY { <optional> ARRAY </optional> | OBJECT } | DEFAULT <replaceable class="parameter">expression</replaceable> } ON EMPTY </optional>
+ <optional> { ERROR | NULL | EMPTY { <optional> ARRAY </optional> | OBJECT } | DEFAULT <replaceable class="parameter">expression</replaceable> } ON ERROR </optional>
+)
+ </synopsis>
+
+ <sect5>
+ <title>Description</title>
+
+ <para>
+ <function>JSON_QUERY</function> function extracts an <acronym>SQL/JSON</acronym>
+ array or object from <acronym>JSON</acronym> data. This function must return
+ a JSON string, so if the path expression returns a scalar or multiple SQL/JSON
+ items, you must wrap the result using the <literal>WITH WRAPPER</literal> clause.
+ To extract a single <acronym>SQL/JSON</acronym> value, you can use <xref linkend="functions-jsonvalue"/>.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Parameters</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>
+ <literal><replaceable>context_item</replaceable>, <replaceable>path_expression</replaceable> <optional> PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } <optional>, ...</optional></optional></literal>
+ </term>
+ <listitem>
+
+ <para>
+ The input data to query, the JSON path expression defining the query, and an optional <literal>PASSING</literal> clause.
+ For details, see <xref linkend="functions-sqljson-path"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional></literal>
+ </term>
+ <listitem>
+ <para>
+ The output clause that specifies the data type of the returned value.
+ For details, see <xref linkend="sqljson-output-clause"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ WITHOUT | WITH { CONDITIONAL | <optional>UNCONDITIONAL</optional> } } <optional> ARRAY </optional> WRAPPER</literal>
+ </term>
+ <listitem>
+ <para>
+ Defines whether to wrap a returned sequence of <acronym>SQL/JSON</acronym>
+ items into a <acronym>SQL/JSON</acronym> array.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><literal>WITHOUT WRAPPER</literal></term>
+ <listitem>
+ <para>
+ Do not wrap the result.
+ This is the default behavior if the <literal>WRAPPER</literal>
+ clause is omitted.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>WITH <optional>UNCONDITIONAL</optional> WRAPPER</literal></term>
+ <listitem>
+ <para>
+ Always wrap the result.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>WITH CONDITIONAL WRAPPER</literal></term>
+ <listitem>
+ <para>
+ Wrap the result if the path
+ expression returns anything other than a single
+ <acronym>SQL/JSON</acronym> array or object.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ Optionally, you can add the <literal>ARRAY</literal> keyword for semantic clarity.
+ </para>
+ <important>
+ <para>You cannot use this clause together with the <literal>ON EMPTY</literal> clause.
+ </para>
+ </important>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ KEEP | OMIT } QUOTES <optional> ON SCALAR STRING </optional></literal>
+ </term>
+ <listitem>
+ <para>
+ Defines whether to keep or omit quotes if a scalar string is returned.
+ By default, scalar strings are returned with quotes. Using this
+ clause together with the <command>WITH WRAPPER</command> clause is not allowed.
+ </para>
+ <para>
+ Optionally, you can add the <literal>ON SCALAR STRING</literal> keywords for semantic clarity.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ ERROR | NULL | EMPTY { <optional> ARRAY </optional> | OBJECT } | DEFAULT <replaceable class="parameter">expression</replaceable> } ON EMPTY</literal>
+ </term>
+ <listitem>
+ <para>
+ Defines the return value if no JSON value is found. The default is <literal>NULL</literal>.
+ If you use <literal>EMPTY <optional>ARRAY</optional></literal> or <literal>EMPTY OBJECT</literal>,
+ an empty JSON array [] or object {} is returned, respectively.
+ If you use <literal>DEFAULT <replaceable class="parameter">expression</replaceable></literal>,
+ the provided <replaceable class="parameter">expression</replaceable> is evaluated and cast
+ to the type specified in the <command>RETURNING</command> clause.
+ </para>
+ <para>
+ You cannot use this clause together with the <literal>WRAPPER</literal> clause.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ ERROR | NULL | EMPTY { <optional> ARRAY </optional> | OBJECT } | DEFAULT <replaceable class="parameter">expression</replaceable> } ON ERROR</literal>
+ </term>
+ <listitem>
+ <para>
+ Defines the return value if an unhandled error occurs. The default is <literal>NULL</literal>.
+ If you use <literal>EMPTY <optional>ARRAY</optional></literal> or <literal>EMPTY OBJECT</literal>,
+ an empty JSON array <literal>[]</literal> or object <literal>{}</literal> are returned, respectively.
+ If you use <literal>DEFAULT <replaceable class="parameter">expression</replaceable></literal>,
+ the provided <replaceable class="parameter">expression</replaceable> is evaluated and cast
+ to the type specified in the <command>RETURNING</command> clause.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect5>
+
+ <sect5>
+ <title>Examples</title>
+
+ <para>
+ Extract all film genres listed in the <structname>my_films</structname> table:
+ </para>
+ <screen>
+SELECT
+ JSON_QUERY(js, '$.favorites[*].kind' WITH WRAPPER ERROR ON ERROR)
+FROM my_films;
+ json_query
+------------
+ ["comedy", "horror", "thriller", "drama"]
+(1 row)
+</screen>
+
+ <para>
+ Note that the same query will result in an error if you omit the
+ <command>WITH WRAPPER</command> clause, as it returns multiple SQL/JSON items:
+ </para>
+ <screen>
+SELECT
+ JSON_QUERY(js, '$.favorites[*].kind' ERROR ON ERROR)
+FROM my_films;
+ERROR: more than one SQL/JSON item
+</screen>
+
+ <para>
+ Compare the effect of different <literal>WRAPPER</literal> clauses:
+ </para>
+ <screen>
+SELECT
+ js,
+ JSON_QUERY(js, 'lax $[*]') AS "without",
+ JSON_QUERY(js, 'lax $[*]' WITH WRAPPER) AS "with uncond",
+ JSON_QUERY(js, 'lax $[*]' WITH CONDITIONAL WRAPPER) AS "with cond"
+FROM
+ (VALUES (jsonb '[]'), ('[1]'), ('[[1,2,3]]'), ('[{"a": 1}]'), ('[1, null, "2"]')) foo(js);
+ js | without | with uncond | with cond
+----------------+-----------+----------------+----------------
+ [] | (null) | (null) | (null)
+ [1] | 1 | [1] | [1]
+ [[1, 2, 3]] | [1, 2, 3] | [[1, 2, 3]] | [1, 2, 3]
+ [{"a": 1}] | {"a": 1} | [{"a": 1}] | {"a": 1}
+ [1, null, "2"] | (null) | [1, null, "2"] | [1, null, "2"]
+(5 rows)
+</screen>
+
+<para>Compare quote handling for scalar types with and without the <command>OMIT QUOTES</command> clause:
+</para>
+ <screen>
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text);
+ json_query
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES);
+ json_query
+------------
+ aaa
+(1 row)
+</screen>
+ </sect5>
+ </sect4>
+
+ <sect4 id="functions-isjson-predicate">
+ <title><literal>IS JSON</literal></title>
+ <indexterm><primary>is_json</primary></indexterm>
+
+<synopsis>
+<replaceable class="parameter">expression</replaceable>
+ IS <optional> NOT </optional> JSON
+ <optional> { VALUE | SCALAR | ARRAY | OBJECT } </optional>
+ <optional> { WITH | WITHOUT } UNIQUE <optional> KEYS </optional> </optional>
+</synopsis>
+
+ <sect5>
+ <title>Description</title>
+
+ <para>
+ The <command>IS JSON</command> predicate tests whether the provided value is valid
+ <acronym>JSON</acronym> data. If you provide a specific JSON data type as a parameter,
+ you can check whether the value belongs to this type.
+ You can also use this predicate in the <command>IS NOT JSON</command> form.
+ The return values are:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>t</literal> if the value satisfies the specified condition.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>f</literal> if the value does not satisfy the specified condition.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Parameters</title>
+
+<variablelist>
+
+ <varlistentry>
+ <term>
+ <literal><replaceable class="parameter">expression</replaceable></literal>
+ </term>
+ <listitem>
+
+ <para>
+ The input clause defining the value to test. You can provide the values
+ of <literal>json</literal>, <literal>jsonb</literal>,
+ <literal>bytea</literal>, or character string types.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>VALUE | SCALAR | ARRAY | OBJECT</literal>
+ </term>
+ <listitem>
+
+ <para>
+ Specifies the <acronym>JSON</acronym> data type to test for:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>VALUE</literal> (default) — any <acronym>JSON</acronym> type.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>SCALAR</literal> — <acronym>JSON</acronym> number, string, or boolean.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>ARRAY</literal> — <acronym>JSON</acronym> array.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>OBJECT</literal> — <acronym>JSON</acronym> object.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>{ WITH | WITHOUT } UNIQUE <optional> KEYS </optional></literal>
+ </term>
+ <listitem>
+ <para>Defines whether duplicate keys are allowed:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>WITHOUT</literal> (default) — the
+ <acronym>JSON</acronym> object can contain duplicate keys.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>WITH</literal> — duplicate keys are not allowed.
+ If the input data contains duplicate keys, it is considered to be invalid JSON.
+ </para>
+ </listitem>
+ </itemizedlist>
+ Optionally, you can add the <literal>KEYS</literal> keyword for semantic clarity.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </sect5>
+
+ <sect5>
+ <title>Examples</title>
+
+ <para>
+ Compare the result returned by the <function>IS JSON</function>
+ predicate for different data types:
+ </para>
+ <screen>
+SELECT
+ js,
+ js IS JSON "is json",
+ js IS NOT JSON "is not json",
+ js IS JSON SCALAR "is scalar",
+ js IS JSON OBJECT "is object",
+ js IS JSON ARRAY "is array"
+FROM
+ (VALUES ('123'), ('"abc"'), ('{"a": "b"}'), ('[1,2]'), ('abc')) foo(js);
+
+ js | is json | is not json | is scalar | is object | is array
+------------+---------+-------------+-----------+-----------|-------------
+ 123 | t | f | t | f | f
+ "abc" | t | f | t | f | f
+ {"a": "b"} | t | f | f | t | f
+ [1,2] | t | f | f | f | t
+ abc | f | t | f | f | f
+(5 rows)
+</screen>
+ </sect5>
+ </sect4>
+
+
+ </sect3>
+
+ <sect3 id="functions-sqljson-serializing">
+ <title>Serializing JSON data</title>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <link linkend="functions-jsonserialize"><literal>JSON_SERIALIZE</literal></link>
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <sect4 id="functions-jsonserialize">
+ <title><literal>JSON_SERIALAIZE</literal></title>
+ <indexterm><primary>json_serialize</primary></indexterm>
+
+<synopsis>
+JSON_SERIALIZE (
+ <parameter>expression</parameter> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional>
+ <optional> RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional> </optional>
+)
+</synopsis>
+
+ <sect5>
+ <title>Description</title>
+
+ <para>
+ <function>JSON_SERIALIZE</function> function transforms a SQL/JSON value
+ into a character or binary string.
+ </para>
+ </sect5>
+
+ <sect5>
+ <title>Parameters</title>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal><parameter>expression</parameter> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional></literal>
+ </term>
+ <listitem>
+ <para>
+ <acronym>JSON</acronym> typed expression that provides a data for
+ serialization. Accepted JSON types (<type>json</type> and
+ <type>jsonb</type>), any character string types (<type>text</type>,
+ <type>char</type>, etc.), binary strings (<type>bytea</type>) in
+ UTF8 encoding.
+ For null input, null value is returned.
+ </para>
+ <para>
+ The optional <literal>FORMAT</literal> clause is provided to conform
+ to the SQL/JSON standard.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional></literal>
+ </term>
+ <listitem>
+ <para>
+ The output clause that specifies the target character or binary string
+ type (<type>text</type>, <type>char</type>, <type>bytea</type>, etc.).
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect5>
+
+ <sect5>
+ <title>Notes</title>
+ <para>
+ Alternatively, you can construct <acronym>JSON</acronym> values simply
+ using <productname>PostgreSQL</productname>-specific casts to
+ <type>json</type> and <type>jsonb</type> types.
+ </para>
+ </sect5>
+ <sect5>
+ <title>Examples</title>
+ <para>
+ Construct a JSON the provided strings:
+ </para>
+<screen>
+SELECT JSON_SERIALIZE(JSON_SCALAR('foo'));
+ json_serialize
+----------------
+ "foo"
+(1 row)
+
+SELECT JSON_SERIALIZE('{"foo": "bar", "baz": [1, 2, 3]}' RETURNING bytea);
+ json_serialize
+--------------------------------------------------------------------
+ \x7b22666f6f223a2022626172222c202262617a223a205b312c20322c20335d7d
+(1 row)
+
+</screen>
+ </sect5>
+ </sect4>
+
+ </sect3>
+
+ <sect3 id="sqljson-common-clauses">
+ <title>SQL/JSON Common Clauses</title>
+
+ <sect4 id="sqljson-input-clause">
+ <title>SQL/JSON Input Clause</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal><replaceable>context_item</replaceable>, <replaceable>path_expression</replaceable>
+<optional> PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } <optional>, ...</optional></optional></literal>
+ </term>
+ <listitem>
+ <para>
+ The input clause specifies the JSON data to query and
+ the exact query path to be passed to SQL/JSON query functions:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ The <replaceable>context_item</replaceable> is the JSON data to query.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The <replaceable>path_expression</replaceable> is an SQL/JSON path
+ expression that specifies the items to be retrieved from the JSON
+ data. For details on path expression syntax, see
+ <xref linkend="functions-sqljson-path"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The optional <command>PASSING</command> clause provides the values for
+ the named variables used in the SQL/JSON path expression.
+ </para>
+ </listitem>
+ </itemizedlist>
+ <para>
+ The input clause is common for all SQL/JSON query functions.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </sect4>
+
+ <sect4 id="sqljson-output-clause">
+ <title>SQL/JSON Output Clause</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional></literal>
+ </term>
+ <listitem>
+ <para>
+ The output clause that specifies the return type of the generated
+ <acronym>JSON</acronym> object. Out of the box, <productname>PostgreSQL</productname>
+ supports the following types: <type>json</type>, <type>jsonb</type>,
+ <type>bytea</type>, and character string types (<type>text</type>, <type>char</type>,
+ <type>varchar</type>, and <type>nchar</type>).
+ To use other types, you must create the <literal>CAST</literal> from <type>json</type> for this type.
+ By default, the <type>json</type> type is returned.
+ </para>
+ <para>
+ The optional <literal>FORMAT</literal> clause is provided to conform to the SQL/JSON standard.
+ </para>
+ <para>
+ The output clause is common for both constructor and query SQL/JSON functions.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </sect4>
+ </sect3>
+ </sect2>
+
</sect1>
<sect1 id="functions-sequence">
@@ -19596,6 +21514,115 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
<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>
+ <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>.
+ Null values are skipped, keys can not be null.
+ </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 not keys.
+ In case of duplicate keys error is thrown.
+ </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>.
+ Null values are skipped, keys can not be null.
+ In case of duplicate keys error is thrown.
+ </para></entry>
+ <entry>No</entry>
+ </row>
<row>
<entry role="func_table_entry"><para role="func_signature">
diff --git a/doc/src/sgml/keywords/sql2016-02-reserved.txt b/doc/src/sgml/keywords/sql2016-02-reserved.txt
index ae11012388..7ba4208398 100644
--- a/doc/src/sgml/keywords/sql2016-02-reserved.txt
+++ b/doc/src/sgml/keywords/sql2016-02-reserved.txt
@@ -156,12 +156,15 @@ INTERVAL
INTO
IS
JOIN
+JSON
JSON_ARRAY
JSON_ARRAYAGG
JSON_EXISTS
JSON_OBJECT
JSON_OBJECTAGG
JSON_QUERY
+JSON_SCALAR
+JSON_SERIALIZE
JSON_TABLE
JSON_TABLE_PRIMITIVE
JSON_VALUE
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 81b9d87bad..efcfde8c95 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -47,6 +47,9 @@
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/datum.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
+#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
@@ -85,6 +88,40 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
bool nullcheck);
+static ExprState *
+ExecInitExprInternal(Expr *node, PlanState *parent, ParamListInfo ext_params,
+ Datum *caseval, bool *casenull)
+{
+ ExprState *state;
+ ExprEvalStep scratch = {0};
+
+ /* Special case: NULL expression produces a NULL ExprState pointer */
+ if (node == NULL)
+ return NULL;
+
+ /* Initialize ExprState with empty step list */
+ state = makeNode(ExprState);
+ state->expr = node;
+ state->parent = parent;
+ state->ext_params = ext_params;
+ state->innermost_caseval = caseval;
+ state->innermost_casenull = casenull;
+
+ /* Insert EEOP_*_FETCHSOME steps as needed */
+ ExecInitExprSlots(state, (Node *) node);
+
+ /* Compile the expression proper */
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+ /* Finally, append a DONE step */
+ scratch.opcode = EEOP_DONE;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
/*
* ExecInitExpr: prepare an expression tree for execution
*
@@ -122,32 +159,7 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
ExprState *
ExecInitExpr(Expr *node, PlanState *parent)
{
- ExprState *state;
- ExprEvalStep scratch = {0};
-
- /* Special case: NULL expression produces a NULL ExprState pointer */
- if (node == NULL)
- return NULL;
-
- /* Initialize ExprState with empty step list */
- state = makeNode(ExprState);
- state->expr = node;
- state->parent = parent;
- state->ext_params = NULL;
-
- /* Insert EEOP_*_FETCHSOME steps as needed */
- ExecInitExprSlots(state, (Node *) node);
-
- /* Compile the expression proper */
- ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
-
- /* Finally, append a DONE step */
- scratch.opcode = EEOP_DONE;
- ExprEvalPushStep(state, &scratch);
-
- ExecReadyExpr(state);
-
- return state;
+ return ExecInitExprInternal(node, parent, NULL, NULL, NULL);
}
/*
@@ -159,32 +171,20 @@ ExecInitExpr(Expr *node, PlanState *parent)
ExprState *
ExecInitExprWithParams(Expr *node, ParamListInfo ext_params)
{
- ExprState *state;
- ExprEvalStep scratch = {0};
-
- /* Special case: NULL expression produces a NULL ExprState pointer */
- if (node == NULL)
- return NULL;
-
- /* Initialize ExprState with empty step list */
- state = makeNode(ExprState);
- state->expr = node;
- state->parent = NULL;
- state->ext_params = ext_params;
-
- /* Insert EEOP_*_FETCHSOME steps as needed */
- ExecInitExprSlots(state, (Node *) node);
-
- /* Compile the expression proper */
- ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
-
- /* Finally, append a DONE step */
- scratch.opcode = EEOP_DONE;
- ExprEvalPushStep(state, &scratch);
-
- ExecReadyExpr(state);
+ return ExecInitExprInternal(node, NULL, ext_params, NULL, NULL);
+}
- return state;
+/*
+ * ExecInitExprWithCaseValue: prepare an expression tree for execution
+ *
+ * This is the same as ExecInitExpr, except that a pointer to the value for
+ * CasTestExpr is passed here.
+ */
+ExprState *
+ExecInitExprWithCaseValue(Expr *node, PlanState *parent,
+ Datum *caseval, bool *casenull)
+{
+ return ExecInitExprInternal(node, parent, NULL, caseval, casenull);
}
/*
@@ -2428,6 +2428,253 @@ 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 if ((ctor->type == JSCTOR_JSON_PARSE && !ctor->unique) ||
+ ctor->type == JSCTOR_JSON_SERIALIZE)
+ {
+ /* Use the value of the first argument as a result */
+ ExecInitExprRec(linitial(args), state, resv, resnull);
+ }
+ else
+ {
+ scratch.opcode = EEOP_JSON_CONSTRUCTOR;
+ scratch.d.json_constructor.constructor = ctor;
+ scratch.d.json_constructor.arg_values = palloc(sizeof(Datum) * nargs);
+ scratch.d.json_constructor.arg_nulls = palloc(sizeof(bool) * nargs);
+ scratch.d.json_constructor.arg_types = palloc(sizeof(Oid) * nargs);
+ scratch.d.json_constructor.nargs = nargs;
+
+ foreach(lc, args)
+ {
+ Expr *arg = (Expr *) lfirst(lc);
+
+ scratch.d.json_constructor.arg_types[argno] = exprType((Node *) arg);
+
+ if (IsA(arg, Const))
+ {
+ /* Don't evaluate const arguments every round */
+ Const *con = (Const *) arg;
+
+ scratch.d.json_constructor.arg_values[argno] = con->constvalue;
+ scratch.d.json_constructor.arg_nulls[argno] = con->constisnull;
+ }
+ else
+ {
+ ExecInitExprRec(arg, state,
+ &scratch.d.json_constructor.arg_values[argno],
+ &scratch.d.json_constructor.arg_nulls[argno]);
+ }
+ argno++;
+ }
+
+ /* prepare type cache for datum_to_json[b]() */
+ if (ctor->type == JSCTOR_JSON_SCALAR)
+ {
+ bool is_jsonb =
+ ctor->returning->format->format == JS_FORMAT_JSONB;
+
+ scratch.d.json_constructor.arg_type_cache =
+ palloc(sizeof(*scratch.d.json_constructor.arg_type_cache) * nargs);
+
+ for (int i = 0; i < nargs; i++)
+ {
+ int category;
+ Oid outfuncid;
+ Oid typid = scratch.d.json_constructor.arg_types[i];
+
+ if (is_jsonb)
+ {
+ JsonbTypeCategory jbcat;
+
+ jsonb_categorize_type(typid, &jbcat, &outfuncid);
+
+ category = (int) jbcat;
+ }
+ else
+ {
+ JsonTypeCategory jscat;
+
+ json_categorize_type(typid, &jscat, &outfuncid);
+
+ category = (int) jscat;
+ }
+
+ scratch.d.json_constructor.arg_type_cache[i].outfuncid = outfuncid;
+ scratch.d.json_constructor.arg_type_cache[i].category = category;
+ }
+ }
+
+ 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_JsonIsPredicate:
+ {
+ JsonIsPredicate *pred = (JsonIsPredicate *) node;
+
+ ExecInitExprRec((Expr *) pred->expr, state, resv, resnull);
+
+ scratch.opcode = EEOP_IS_JSON;
+ scratch.d.is_json.pred = pred;
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_JsonExpr:
+ {
+ JsonExpr *jexpr = castNode(JsonExpr, node);
+ ListCell *argexprlc;
+ ListCell *argnamelc;
+
+ scratch.opcode = EEOP_JSONEXPR;
+ scratch.d.jsonexpr.jsexpr = jexpr;
+
+ scratch.d.jsonexpr.formatted_expr =
+ palloc(sizeof(*scratch.d.jsonexpr.formatted_expr));
+
+ ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
+ &scratch.d.jsonexpr.formatted_expr->value,
+ &scratch.d.jsonexpr.formatted_expr->isnull);
+
+ scratch.d.jsonexpr.pathspec =
+ palloc(sizeof(*scratch.d.jsonexpr.pathspec));
+
+ ExecInitExprRec((Expr *) jexpr->path_spec, state,
+ &scratch.d.jsonexpr.pathspec->value,
+ &scratch.d.jsonexpr.pathspec->isnull);
+
+ scratch.d.jsonexpr.res_expr =
+ palloc(sizeof(*scratch.d.jsonexpr.res_expr));
+
+ scratch.d.jsonexpr.result_expr = jexpr->result_coercion
+ ? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr,
+ state->parent,
+ &scratch.d.jsonexpr.res_expr->value,
+ &scratch.d.jsonexpr.res_expr->isnull)
+ : NULL;
+
+ scratch.d.jsonexpr.default_on_empty = !jexpr->on_empty ? NULL :
+ ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
+ state->parent);
+
+ scratch.d.jsonexpr.default_on_error =
+ ExecInitExpr((Expr *) jexpr->on_error->default_expr,
+ state->parent);
+
+ if (jexpr->omit_quotes ||
+ (jexpr->result_coercion && jexpr->result_coercion->via_io))
+ {
+ Oid typinput;
+
+ /* lookup the result type's input function */
+ getTypeInputInfo(jexpr->returning->typid, &typinput,
+ &scratch.d.jsonexpr.input.typioparam);
+ fmgr_info(typinput, &scratch.d.jsonexpr.input.func);
+ }
+
+ scratch.d.jsonexpr.args = NIL;
+
+ forboth(argexprlc, jexpr->passing_values,
+ argnamelc, jexpr->passing_names)
+ {
+ Expr *argexpr = (Expr *) lfirst(argexprlc);
+ String *argname = lfirst_node(String, argnamelc);
+ JsonPathVariableEvalContext *var = palloc(sizeof(*var));
+
+ var->name = pstrdup(argname->val);
+ var->typid = exprType((Node *) argexpr);
+ var->typmod = exprTypmod((Node *) argexpr);
+ var->estate = ExecInitExpr(argexpr, state->parent);
+ var->econtext = NULL;
+ var->evaluated = false;
+ var->value = (Datum) 0;
+ var->isnull = true;
+
+ scratch.d.jsonexpr.args =
+ lappend(scratch.d.jsonexpr.args, var);
+ }
+
+ scratch.d.jsonexpr.cache = NULL;
+
+ if (jexpr->coercions)
+ {
+ JsonCoercion **coercion;
+ struct JsonCoercionState *cstate;
+ Datum *caseval;
+ bool *casenull;
+
+ scratch.d.jsonexpr.coercion_expr =
+ palloc(sizeof(*scratch.d.jsonexpr.coercion_expr));
+
+ caseval = &scratch.d.jsonexpr.coercion_expr->value;
+ casenull = &scratch.d.jsonexpr.coercion_expr->isnull;
+
+ for (cstate = &scratch.d.jsonexpr.coercions.null,
+ coercion = &jexpr->coercions->null;
+ coercion <= &jexpr->coercions->composite;
+ coercion++, cstate++)
+ {
+ cstate->coercion = *coercion;
+ cstate->estate = *coercion ?
+ ExecInitExprWithCaseValue((Expr *)(*coercion)->expr,
+ state->parent,
+ caseval, casenull) : NULL;
+ }
+ }
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index eb49817cee..0d4d30853e 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -57,22 +57,31 @@
#include "postgres.h"
#include "access/heaptoast.h"
+#include "access/xact.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/sequence.h"
#include "executor/execExpr.h"
#include "executor/nodeSubplan.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parsetree.h"
+#include "parser/parse_expr.h"
#include "pgstat.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datum.h"
#include "utils/expandedrecord.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
+#include "utils/jsonfuncs.h"
+#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/resowner.h"
#include "utils/timestamp.h"
#include "utils/typcache.h"
#include "utils/xml.h"
@@ -477,6 +486,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_GROUPING_FUNC,
&&CASE_EEOP_WINDOW_FUNC,
&&CASE_EEOP_SUBPLAN,
+ &&CASE_EEOP_JSON_CONSTRUCTOR,
+ &&CASE_EEOP_IS_JSON,
+ &&CASE_EEOP_JSONEXPR,
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
&&CASE_EEOP_AGG_DESERIALIZE,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1786,7 +1798,27 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
{
/* too complex for an inline implementation */
ExecEvalAggOrderedTransTuple(state, op, econtext);
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_JSON_CONSTRUCTOR)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalJsonConstructor(state, op, econtext);
+ EEO_NEXT();
+ }
+ EEO_CASE(EEOP_IS_JSON)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalJsonIsPredicate(state, op);
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_JSONEXPR)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalJson(state, op, econtext);
EEO_NEXT();
}
@@ -3900,6 +3932,91 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
}
}
+void
+ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op)
+{
+ JsonIsPredicate *pred = op->d.is_json.pred;
+ Datum js = *op->resvalue;
+ Oid exprtype;
+ bool res;
+
+ if (*op->resnull)
+ {
+ *op->resvalue = BoolGetDatum(false);
+ return;
+ }
+
+ exprtype = exprType(pred->expr);
+
+ if (exprtype == TEXTOID || exprtype == JSONOID)
+ {
+ text *json = DatumGetTextP(js);
+
+ if (pred->value_type == JS_TYPE_ANY)
+ res = true;
+ else
+ {
+ switch (json_get_first_token(json, false))
+ {
+ case JSON_TOKEN_OBJECT_START:
+ res = pred->value_type == JS_TYPE_OBJECT;
+ break;
+ case JSON_TOKEN_ARRAY_START:
+ res = pred->value_type == JS_TYPE_ARRAY;
+ break;
+ case JSON_TOKEN_STRING:
+ case JSON_TOKEN_NUMBER:
+ case JSON_TOKEN_TRUE:
+ case JSON_TOKEN_FALSE:
+ case JSON_TOKEN_NULL:
+ res = pred->value_type == JS_TYPE_SCALAR;
+ break;
+ default:
+ res = false;
+ break;
+ }
+ }
+
+ /*
+ * Do full parsing pass only for uniqueness check or for
+ * JSON text validation.
+ */
+ if (res && (pred->unique_keys || exprtype == TEXTOID))
+ res = json_validate(json, pred->unique_keys, false);
+ }
+ else if (exprtype == JSONBOID)
+ {
+ if (pred->value_type == JS_TYPE_ANY)
+ res = true;
+ else
+ {
+ Jsonb *jb = DatumGetJsonbP(js);
+
+ switch (pred->value_type)
+ {
+ case JS_TYPE_OBJECT:
+ res = JB_ROOT_IS_OBJECT(jb);
+ break;
+ case JS_TYPE_ARRAY:
+ res = JB_ROOT_IS_ARRAY(jb) && !JB_ROOT_IS_SCALAR(jb);
+ break;
+ case JS_TYPE_SCALAR:
+ res = JB_ROOT_IS_ARRAY(jb) && JB_ROOT_IS_SCALAR(jb);
+ break;
+ default:
+ res = false;
+ break;
+ }
+ }
+
+ /* Key uniqueness check is redundant for jsonb */
+ }
+ else
+ res = false;
+
+ *op->resvalue = BoolGetDatum(res);
+}
+
/*
* ExecEvalGroupingFunc
*
@@ -4386,3 +4503,612 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
MemoryContextSwitchTo(oldContext);
}
+
+/*
+ * Evaluate a JSON constructor expression.
+ */
+void
+ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext)
+{
+ Datum res;
+ JsonConstructorExpr *ctor = op->d.json_constructor.constructor;
+ bool is_jsonb = ctor->returning->format->format == JS_FORMAT_JSONB;
+ bool isnull = false;
+
+ if (ctor->type == JSCTOR_JSON_ARRAY)
+ res = (is_jsonb ?
+ jsonb_build_array_worker :
+ json_build_array_worker)(op->d.json_constructor.nargs,
+ op->d.json_constructor.arg_values,
+ op->d.json_constructor.arg_nulls,
+ op->d.json_constructor.arg_types,
+ op->d.json_constructor.constructor->absent_on_null);
+ else if (ctor->type == JSCTOR_JSON_OBJECT)
+ res = (is_jsonb ?
+ jsonb_build_object_worker :
+ json_build_object_worker)(op->d.json_constructor.nargs,
+ op->d.json_constructor.arg_values,
+ op->d.json_constructor.arg_nulls,
+ op->d.json_constructor.arg_types,
+ op->d.json_constructor.constructor->absent_on_null,
+ op->d.json_constructor.constructor->unique);
+ else if (ctor->type == JSCTOR_JSON_SCALAR)
+ {
+ if (op->d.json_constructor.arg_nulls[0])
+ {
+ res = (Datum) 0;
+ isnull = true;
+ }
+ else
+ {
+ Datum value = op->d.json_constructor.arg_values[0];
+ int category = op->d.json_constructor.arg_type_cache[0].category;
+ Oid outfuncid = op->d.json_constructor.arg_type_cache[0].outfuncid;
+
+ if (is_jsonb)
+ res = to_jsonb_worker(value, category, outfuncid);
+ else
+ res = to_json_worker(value, category, outfuncid);
+ }
+ }
+ else if (ctor->type == JSCTOR_JSON_PARSE)
+ {
+ if (op->d.json_constructor.arg_nulls[0])
+ {
+ res = (Datum) 0;
+ isnull = true;
+ }
+ else
+ {
+ Datum value = op->d.json_constructor.arg_values[0];
+ text *js = DatumGetTextP(value);
+
+ if (is_jsonb)
+ res = jsonb_from_text(js, true);
+ else
+ {
+ (void) json_validate(js, true, true);
+ res = value;
+ }
+ }
+ }
+ else
+ {
+ res = (Datum) 0;
+ elog(ERROR, "invalid JsonConstructorExpr type %d", ctor->type);
+ }
+
+ *op->resvalue = res;
+ *op->resnull = isnull;
+}
+
+/*
+ * Evaluate a JSON error/empty behavior result.
+ */
+static Datum
+ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
+ ExprState *default_estate, bool *is_null)
+{
+ *is_null = false;
+
+ switch (behavior->btype)
+ {
+ case JSON_BEHAVIOR_EMPTY_ARRAY:
+ return JsonbPGetDatum(JsonbMakeEmptyArray());
+
+ case JSON_BEHAVIOR_EMPTY_OBJECT:
+ return JsonbPGetDatum(JsonbMakeEmptyObject());
+
+ case JSON_BEHAVIOR_TRUE:
+ return BoolGetDatum(true);
+
+ case JSON_BEHAVIOR_FALSE:
+ return BoolGetDatum(false);
+
+ case JSON_BEHAVIOR_NULL:
+ case JSON_BEHAVIOR_UNKNOWN:
+ *is_null = true;
+ return (Datum) 0;
+
+ case JSON_BEHAVIOR_DEFAULT:
+ return ExecEvalExpr(default_estate, econtext, is_null);
+
+ default:
+ elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);
+ return (Datum) 0;
+ }
+}
+
+/*
+ * Evaluate a coercion of a JSON item to the target type.
+ */
+static Datum
+ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext,
+ Datum res, bool *isNull, void *p, bool *error)
+{
+ ExprState *estate = p;
+
+ if (estate) /* coerce using specified expression */
+ return ExecEvalExpr(estate, econtext, isNull);
+
+ if (op->d.jsonexpr.jsexpr->op != IS_JSON_EXISTS)
+ {
+ JsonCoercion *coercion = op->d.jsonexpr.jsexpr->result_coercion;
+ JsonExpr *jexpr = op->d.jsonexpr.jsexpr;
+ Jsonb *jb = *isNull ? NULL : DatumGetJsonbP(res);
+
+ if ((coercion && coercion->via_io) ||
+ (jexpr->omit_quotes && !*isNull &&
+ JB_ROOT_IS_SCALAR(jb)))
+ {
+ /* strip quotes and call typinput function */
+ char *str = *isNull ? NULL : JsonbUnquote(jb);
+
+ return InputFunctionCall(&op->d.jsonexpr.input.func, str,
+ op->d.jsonexpr.input.typioparam,
+ jexpr->returning->typmod);
+ }
+ else if (coercion && coercion->via_populate)
+ return json_populate_type(res, JSONBOID,
+ jexpr->returning->typid,
+ jexpr->returning->typmod,
+ &op->d.jsonexpr.cache,
+ econtext->ecxt_per_query_memory,
+ isNull);
+ }
+
+ if (op->d.jsonexpr.result_expr)
+ {
+ op->d.jsonexpr.res_expr->value = res;
+ op->d.jsonexpr.res_expr->isnull = *isNull;
+
+ res = ExecEvalExpr(op->d.jsonexpr.result_expr, econtext, isNull);
+ }
+
+ return res;
+}
+
+/*
+ * Evaluate a JSON path variable caching computed value.
+ */
+int
+EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
+ JsonbValue *val, JsonbValue *baseObject)
+{
+ JsonPathVariableEvalContext *var = NULL;
+ List *vars = cxt;
+ ListCell *lc;
+ int id = 1;
+
+ if (!varName)
+ return list_length(vars);
+
+ foreach(lc, vars)
+ {
+ var = lfirst(lc);
+
+ if (!strncmp(var->name, varName, varNameLen))
+ break;
+
+ var = NULL;
+ id++;
+ }
+
+ if (!var)
+ return -1;
+
+ if (!var->evaluated)
+ {
+ var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
+ var->evaluated = true;
+ }
+
+ if (var->isnull)
+ {
+ val->type = jbvNull;
+ return 0;
+ }
+
+ JsonItemFromDatum(var->value, var->typid, var->typmod, val);
+
+ *baseObject = *val;
+ return id;
+}
+
+/*
+ * Prepare SQL/JSON item coercion to the output type. Returned a datum of the
+ * corresponding SQL type and a pointer to the coercion state.
+ */
+Datum
+ExecPrepareJsonItemCoercion(JsonbValue *item,
+ JsonReturning *returning,
+ struct JsonCoercionsState *coercions,
+ struct JsonCoercionState **pcoercion)
+{
+ struct JsonCoercionState *coercion;
+ Datum res;
+ JsonbValue buf;
+
+ if (item->type == jbvBinary &&
+ JsonContainerIsScalar(item->val.binary.data))
+ {
+ bool res PG_USED_FOR_ASSERTS_ONLY;
+
+ res = JsonbExtractScalar(item->val.binary.data, &buf);
+ item = &buf;
+ Assert(res);
+ }
+
+ /* get coercion state reference and datum of the corresponding SQL type */
+ switch (item->type)
+ {
+ case jbvNull:
+ coercion = &coercions->null;
+ res = (Datum) 0;
+ break;
+
+ case jbvString:
+ coercion = &coercions->string;
+ res = PointerGetDatum(
+ cstring_to_text_with_len(item->val.string.val,
+ item->val.string.len));
+ break;
+
+ case jbvNumeric:
+ coercion = &coercions->numeric;
+ res = NumericGetDatum(item->val.numeric);
+ break;
+
+ case jbvBool:
+ coercion = &coercions->boolean;
+ res = BoolGetDatum(item->val.boolean);
+ break;
+
+ case jbvDatetime:
+ res = item->val.datetime.value;
+ switch (item->val.datetime.typid)
+ {
+ case DATEOID:
+ coercion = &coercions->date;
+ break;
+ case TIMEOID:
+ coercion = &coercions->time;
+ break;
+ case TIMETZOID:
+ coercion = &coercions->timetz;
+ break;
+ case TIMESTAMPOID:
+ coercion = &coercions->timestamp;
+ break;
+ case TIMESTAMPTZOID:
+ coercion = &coercions->timestamptz;
+ break;
+ default:
+ elog(ERROR, "unexpected jsonb datetime type oid %d",
+ item->val.datetime.typid);
+ return (Datum) 0;
+ }
+ break;
+
+ case jbvArray:
+ case jbvObject:
+ case jbvBinary:
+ coercion = &coercions->composite;
+ res = JsonbPGetDatum(JsonbValueToJsonb(item));
+ break;
+
+ default:
+ elog(ERROR, "unexpected jsonb value type %d", item->type);
+ return (Datum) 0;
+ }
+
+ *pcoercion = coercion;
+
+ return res;
+}
+
+typedef Datum (*JsonFunc)(ExprEvalStep *op, ExprContext *econtext,
+ Datum item, bool *resnull, void *p, bool *error);
+
+static Datum
+ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,
+ ExprContext *econtext,
+ Datum res, bool *resnull,
+ void *p, bool *error, bool subtrans)
+{
+ MemoryContext oldcontext;
+ ResourceOwner oldowner;
+
+ if (!subtrans)
+ /* No need to use subtransactions. */
+ return func(op, econtext, res, resnull, p, error);
+
+ /*
+ * We should catch exceptions of category ERRCODE_DATA_EXCEPTION
+ * and execute the corresponding ON ERROR behavior then.
+ */
+ oldcontext = CurrentMemoryContext;
+ oldowner = CurrentResourceOwner;
+
+ Assert(error);
+
+ BeginInternalSubTransaction(NULL);
+ /* Want to execute expressions inside function's memory context */
+ MemoryContextSwitchTo(oldcontext);
+
+ PG_TRY();
+ {
+ res = func(op, econtext, res, resnull, p, error);
+
+ /* Commit the inner transaction, return to outer xact context */
+ ReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info in oldcontext */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Abort the inner transaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
+ if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) !=
+ ERRCODE_DATA_EXCEPTION)
+ ReThrowError(edata);
+
+ res = (Datum) 0;
+ *error = true;
+ }
+ PG_END_TRY();
+
+ return res;
+}
+
+
+typedef struct
+{
+ JsonPath *path;
+ bool *error;
+ bool coercionInSubtrans;
+} ExecEvalJsonExprContext;
+
+static Datum
+ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
+ Datum item, bool *resnull, void *pcxt,
+ bool *error)
+{
+ ExecEvalJsonExprContext *cxt = pcxt;
+ JsonPath *path = cxt->path;
+ JsonExpr *jexpr = op->d.jsonexpr.jsexpr;
+ ExprState *estate = NULL;
+ bool empty = false;
+ Datum res = (Datum) 0;
+
+ switch (jexpr->op)
+ {
+ case IS_JSON_QUERY:
+ res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error,
+ op->d.jsonexpr.args);
+ if (error && *error)
+ {
+ *resnull = true;
+ return (Datum) 0;
+ }
+ *resnull = !DatumGetPointer(res);
+ break;
+
+ case IS_JSON_VALUE:
+ {
+ struct JsonCoercionState *jcstate;
+ JsonbValue *jbv = JsonPathValue(item, path, &empty, error,
+ op->d.jsonexpr.args);
+
+ if (error && *error)
+ return (Datum) 0;
+
+ if (!jbv) /* NULL or empty */
+ break;
+
+ Assert(!empty);
+
+ *resnull = false;
+
+ /* coerce scalar item to the output type */
+ if (jexpr->returning->typid == JSONOID ||
+ jexpr->returning->typid == JSONBOID)
+ {
+ /* Use result coercion from json[b] to the output type */
+ res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
+ break;
+ }
+
+ /* Use coercion from SQL/JSON item type to the output type */
+ res = ExecPrepareJsonItemCoercion(jbv,
+ op->d.jsonexpr.jsexpr->returning,
+ &op->d.jsonexpr.coercions,
+ &jcstate);
+
+ if (jcstate->coercion &&
+ (jcstate->coercion->via_io ||
+ jcstate->coercion->via_populate))
+ {
+ if (error)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+ /*
+ * Coercion via I/O means here that the cast to the target
+ * type simply does not exist.
+ */
+ ereport(ERROR,
+ /*
+ * XXX Standard says about a separate error code
+ * ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE
+ * but does not define its number.
+ */
+ (errcode(ERRCODE_SQL_JSON_SCALAR_REQUIRED),
+ errmsg("SQL/JSON item cannot be cast to target type")));
+ }
+ else if (!jcstate->estate)
+ return res; /* no coercion */
+
+ /* coerce using specific expression */
+ estate = jcstate->estate;
+ op->d.jsonexpr.coercion_expr->value = res;
+ op->d.jsonexpr.coercion_expr->isnull = *resnull;
+ break;
+ }
+
+ case IS_JSON_EXISTS:
+ {
+ bool exists = JsonPathExists(item, path,
+ op->d.jsonexpr.args,
+ error);
+
+ *resnull = error && *error;
+ res = BoolGetDatum(exists);
+
+ if (!op->d.jsonexpr.result_expr)
+ return res;
+
+ /* coerce using result expression */
+ estate = op->d.jsonexpr.result_expr;
+ op->d.jsonexpr.res_expr->value = res;
+ op->d.jsonexpr.res_expr->isnull = *resnull;
+ break;
+ }
+
+ default:
+ elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
+ return (Datum) 0;
+ }
+
+ if (empty)
+ {
+ Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
+
+ if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
+ {
+ if (error)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_SQL_JSON_ITEM),
+ errmsg("no SQL/JSON item")));
+ }
+
+ if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT)
+ /*
+ * Execute DEFAULT expression as a coercion expression, because
+ * its result is already coerced to the target type.
+ */
+ estate = op->d.jsonexpr.default_on_empty;
+ else
+ /* Execute ON EMPTY behavior */
+ res = ExecEvalJsonBehavior(econtext, jexpr->on_empty,
+ op->d.jsonexpr.default_on_empty,
+ resnull);
+ }
+
+ return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
+ res, resnull, estate, error,
+ cxt->coercionInSubtrans);
+}
+
+bool
+ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
+ struct JsonCoercionsState *coercions)
+{
+ if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+ return false;
+
+ if (jsexpr->op == IS_JSON_EXISTS && !jsexpr->result_coercion)
+ return false;
+
+ if (!coercions)
+ return true;
+
+ return false;
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalJson
+ * ----------------------------------------------------------------
+ */
+void
+ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+ ExecEvalJsonExprContext cxt;
+ JsonExpr *jexpr = op->d.jsonexpr.jsexpr;
+ Datum item;
+ Datum res = (Datum) 0;
+ JsonPath *path;
+ ListCell *lc;
+ bool error = false;
+ bool needSubtrans;
+ bool throwErrors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR;
+
+ *op->resnull = true; /* until we get a result */
+ *op->resvalue = (Datum) 0;
+
+ if (op->d.jsonexpr.formatted_expr->isnull || op->d.jsonexpr.pathspec->isnull)
+ {
+ /* execute domain checks for NULLs */
+ (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull,
+ NULL, NULL);
+
+ Assert(*op->resnull);
+ return;
+ }
+
+ item = op->d.jsonexpr.formatted_expr->value;
+ path = DatumGetJsonPathP(op->d.jsonexpr.pathspec->value);
+
+ /* reset JSON path variable contexts */
+ foreach(lc, op->d.jsonexpr.args)
+ {
+ JsonPathVariableEvalContext *var = lfirst(lc);
+
+ var->econtext = econtext;
+ var->evaluated = false;
+ }
+
+ needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &op->d.jsonexpr.coercions);
+
+ cxt.path = path;
+ cxt.error = throwErrors ? NULL : &error;
+ cxt.coercionInSubtrans = !needSubtrans && !throwErrors;
+ Assert(!needSubtrans || cxt.error);
+
+ res = ExecEvalJsonExprSubtrans(ExecEvalJsonExpr, op, econtext, item,
+ op->resnull, &cxt, cxt.error,
+ needSubtrans);
+
+ if (error)
+ {
+ /* Execute ON ERROR behavior */
+ res = ExecEvalJsonBehavior(econtext, jexpr->on_error,
+ op->d.jsonexpr.default_on_error,
+ op->resnull);
+
+ /* result is already coerced in DEFAULT behavior case */
+ if (jexpr->on_error->btype != JSON_BEHAVIOR_DEFAULT)
+ res = ExecEvalJsonExprCoercion(op, econtext, res,
+ op->resnull,
+ NULL, NULL);
+ }
+
+ *op->resvalue = res;
+}
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 6d1181225e..15c9ee44c0 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2348,6 +2348,24 @@ 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_IS_JSON:
+ build_EvalXFunc(b, mod, "ExecEvalJsonIsPredicate",
+ v_state, op);
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ break;
+
+ case EEOP_JSONEXPR:
+ build_EvalXFunc(b, mod, "ExecEvalJson",
+ v_state, op, v_econtext);
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ break;
+
case EEOP_LAST:
Assert(false);
break;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 2deb65c5b5..b507d98ca1 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -131,6 +131,9 @@ void *referenced_functions[] =
ExecEvalSysVar,
ExecEvalWholeRowVar,
ExecEvalXmlExpr,
+ ExecEvalJsonConstructor,
+ ExecEvalJsonIsPredicate,
+ ExecEvalJson,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 228387eaee..ba2eb0f894 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2295,6 +2295,373 @@ _copyOnConflictExpr(const OnConflictExpr *from)
return newnode;
}
+
+/*
+ * _copyJsonFormat
+ */
+static JsonFormat *
+_copyJsonFormat(const JsonFormat *from)
+{
+ JsonFormat *newnode = makeNode(JsonFormat);
+
+ COPY_SCALAR_FIELD(format);
+ COPY_SCALAR_FIELD(encoding);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonReturning
+ */
+static JsonReturning *
+_copyJsonReturning(const JsonReturning *from)
+{
+ JsonReturning *newnode = makeNode(JsonReturning);
+
+ COPY_NODE_FIELD(format);
+ COPY_SCALAR_FIELD(typid);
+ COPY_SCALAR_FIELD(typmod);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonValueExpr
+ */
+static JsonValueExpr *
+_copyJsonValueExpr(const JsonValueExpr *from)
+{
+ JsonValueExpr *newnode = makeNode(JsonValueExpr);
+
+ COPY_NODE_FIELD(raw_expr);
+ COPY_NODE_FIELD(formatted_expr);
+ COPY_NODE_FIELD(format);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonScalarExpr
+ */
+static JsonScalarExpr *
+_copyJsonScalarExpr(const JsonScalarExpr *from)
+{
+ JsonScalarExpr *newnode = makeNode(JsonScalarExpr);
+
+ COPY_NODE_FIELD(expr);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonSerializeExpr
+ */
+static JsonSerializeExpr *
+_copyJsonSerializeExpr(const JsonSerializeExpr *from)
+{
+ JsonSerializeExpr *newnode = makeNode(JsonSerializeExpr);
+
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(output);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonConstructorExpr
+ */
+static JsonConstructorExpr *
+_copyJsonConstructorExpr(const JsonConstructorExpr *from)
+{
+ JsonConstructorExpr *newnode = makeNode(JsonConstructorExpr);
+
+ COPY_SCALAR_FIELD(type);
+ COPY_NODE_FIELD(args);
+ COPY_NODE_FIELD(func);
+ COPY_NODE_FIELD(coercion);
+ COPY_NODE_FIELD(returning);
+ COPY_SCALAR_FIELD(absent_on_null);
+ COPY_SCALAR_FIELD(unique);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonKeyValue
+ */
+static JsonKeyValue *
+_copyJsonKeyValue(const JsonKeyValue *from)
+{
+ JsonKeyValue *newnode = makeNode(JsonKeyValue);
+
+ COPY_NODE_FIELD(key);
+ COPY_NODE_FIELD(value);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonObjectConstructor
+ */
+static JsonObjectConstructor *
+_copyJsonObjectConstructor(const JsonObjectConstructor *from)
+{
+ JsonObjectConstructor *newnode = makeNode(JsonObjectConstructor);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_NODE_FIELD(output);
+ COPY_SCALAR_FIELD(absent_on_null);
+ COPY_SCALAR_FIELD(unique);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonAggConstructor
+ */
+static JsonAggConstructor *
+_copyJsonAggConstructor(const JsonAggConstructor *from)
+{
+ JsonAggConstructor *newnode = makeNode(JsonAggConstructor);
+
+ COPY_NODE_FIELD(output);
+ COPY_NODE_FIELD(agg_filter);
+ COPY_NODE_FIELD(agg_order);
+ COPY_NODE_FIELD(over);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonObjectAgg
+ */
+static JsonObjectAgg *
+_copyJsonObjectAgg(const JsonObjectAgg *from)
+{
+ JsonObjectAgg *newnode = makeNode(JsonObjectAgg);
+
+ COPY_NODE_FIELD(constructor);
+ COPY_NODE_FIELD(arg);
+ COPY_SCALAR_FIELD(absent_on_null);
+ COPY_SCALAR_FIELD(unique);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonOutput
+ */
+static JsonOutput *
+_copyJsonOutput(const JsonOutput *from)
+{
+ JsonOutput *newnode = makeNode(JsonOutput);
+
+ COPY_NODE_FIELD(typeName);
+ COPY_NODE_FIELD(returning);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonArrayConstructor
+ */
+static JsonArrayConstructor *
+_copyJsonArrayConstructor(const JsonArrayConstructor *from)
+{
+ JsonArrayConstructor *newnode = makeNode(JsonArrayConstructor);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_NODE_FIELD(output);
+ COPY_SCALAR_FIELD(absent_on_null);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonArrayAgg
+ */
+static JsonArrayAgg *
+_copyJsonArrayAgg(const JsonArrayAgg *from)
+{
+ JsonArrayAgg *newnode = makeNode(JsonArrayAgg);
+
+ COPY_NODE_FIELD(constructor);
+ COPY_NODE_FIELD(arg);
+ COPY_SCALAR_FIELD(absent_on_null);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonArrayQueryConstructor
+ */
+static JsonArrayQueryConstructor *
+_copyJsonArrayQueryConstructor(const JsonArrayQueryConstructor *from)
+{
+ JsonArrayQueryConstructor *newnode = makeNode(JsonArrayQueryConstructor);
+
+ COPY_NODE_FIELD(query);
+ COPY_NODE_FIELD(output);
+ COPY_NODE_FIELD(format);
+ COPY_SCALAR_FIELD(absent_on_null);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonExpr
+ */
+static JsonExpr *
+_copyJsonExpr(const JsonExpr *from)
+{
+ JsonExpr *newnode = makeNode(JsonExpr);
+
+ COPY_SCALAR_FIELD(op);
+ COPY_NODE_FIELD(formatted_expr);
+ COPY_NODE_FIELD(result_coercion);
+ COPY_NODE_FIELD(format);
+ COPY_NODE_FIELD(path_spec);
+ COPY_NODE_FIELD(passing_values);
+ COPY_NODE_FIELD(passing_names);
+ COPY_NODE_FIELD(returning);
+ COPY_NODE_FIELD(on_error);
+ COPY_NODE_FIELD(on_empty);
+ COPY_NODE_FIELD(coercions);
+ COPY_SCALAR_FIELD(wrapper);
+ COPY_SCALAR_FIELD(omit_quotes);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonCoercion
+ */
+static JsonCoercion *
+_copyJsonCoercion(const JsonCoercion *from)
+{
+ JsonCoercion *newnode = makeNode(JsonCoercion);
+
+ COPY_NODE_FIELD(expr);
+ COPY_SCALAR_FIELD(via_populate);
+ COPY_SCALAR_FIELD(via_io);
+ COPY_SCALAR_FIELD(collation);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonItemCoercions
+ */
+static JsonItemCoercions *
+_copyJsonItemCoercions(const JsonItemCoercions *from)
+{
+ JsonItemCoercions *newnode = makeNode(JsonItemCoercions);
+
+ COPY_NODE_FIELD(null);
+ COPY_NODE_FIELD(string);
+ COPY_NODE_FIELD(numeric);
+ COPY_NODE_FIELD(boolean);
+ COPY_NODE_FIELD(date);
+ COPY_NODE_FIELD(time);
+ COPY_NODE_FIELD(timetz);
+ COPY_NODE_FIELD(timestamp);
+ COPY_NODE_FIELD(timestamptz);
+ COPY_NODE_FIELD(composite);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonFuncExpr
+ */
+static JsonFuncExpr *
+_copyJsonFuncExpr(const JsonFuncExpr *from)
+{
+ JsonFuncExpr *newnode = makeNode(JsonFuncExpr);
+
+ COPY_SCALAR_FIELD(op);
+ COPY_NODE_FIELD(common);
+ COPY_NODE_FIELD(output);
+ COPY_NODE_FIELD(on_empty);
+ COPY_NODE_FIELD(on_error);
+ COPY_SCALAR_FIELD(wrapper);
+ COPY_SCALAR_FIELD(omit_quotes);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonIsPredicate
+ */
+static JsonIsPredicate *
+_copyJsonIsPredicate(const JsonIsPredicate *from)
+{
+ JsonIsPredicate *newnode = makeNode(JsonIsPredicate);
+
+ COPY_NODE_FIELD(expr);
+ COPY_SCALAR_FIELD(format);
+ COPY_SCALAR_FIELD(value_type);
+ COPY_SCALAR_FIELD(unique_keys);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonBehavior
+ */
+static JsonBehavior *
+_copyJsonBehavior(const JsonBehavior *from)
+{
+ JsonBehavior *newnode = makeNode(JsonBehavior);
+
+ COPY_SCALAR_FIELD(btype);
+ COPY_NODE_FIELD(default_expr);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonCommon
+ */
+static JsonCommon *
+_copyJsonCommon(const JsonCommon *from)
+{
+ JsonCommon *newnode = makeNode(JsonCommon);
+
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(pathspec);
+ COPY_STRING_FIELD(pathname);
+ COPY_NODE_FIELD(passing);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonArgument
+ */
+static JsonArgument *
+_copyJsonArgument(const JsonArgument *from)
+{
+ JsonArgument *newnode = makeNode(JsonArgument);
+
+ COPY_NODE_FIELD(val);
+ COPY_STRING_FIELD(name);
+
+ return newnode;
+}
+
/* ****************************************************************
* pathnodes.h copy functions
*
@@ -5306,6 +5673,72 @@ copyObjectImpl(const void *from)
case T_OnConflictExpr:
retval = _copyOnConflictExpr(from);
break;
+ case T_JsonFormat:
+ retval = _copyJsonFormat(from);
+ break;
+ case T_JsonReturning:
+ retval = _copyJsonReturning(from);
+ break;
+ case T_JsonValueExpr:
+ retval = _copyJsonValueExpr(from);
+ break;
+ case T_JsonScalarExpr:
+ retval = _copyJsonScalarExpr(from);
+ break;
+ case T_JsonSerializeExpr:
+ retval = _copyJsonSerializeExpr(from);
+ break;
+ case T_JsonKeyValue:
+ retval = _copyJsonKeyValue(from);
+ break;
+ case T_JsonConstructorExpr:
+ retval = _copyJsonConstructorExpr(from);
+ break;
+ case T_JsonObjectConstructor:
+ retval = _copyJsonObjectConstructor(from);
+ break;
+ case T_JsonAggConstructor:
+ retval = _copyJsonAggConstructor(from);
+ break;
+ case T_JsonObjectAgg:
+ retval = _copyJsonObjectAgg(from);
+ break;
+ case T_JsonOutput:
+ retval = _copyJsonOutput(from);
+ break;
+ case T_JsonArrayConstructor:
+ retval = _copyJsonArrayConstructor(from);
+ break;
+ case T_JsonArrayQueryConstructor:
+ retval = _copyJsonArrayQueryConstructor(from);
+ break;
+ case T_JsonArrayAgg:
+ retval = _copyJsonArrayAgg(from);
+ break;
+ case T_JsonIsPredicate:
+ retval = _copyJsonIsPredicate(from);
+ break;
+ case T_JsonFuncExpr:
+ retval = _copyJsonFuncExpr(from);
+ break;
+ case T_JsonExpr:
+ retval = _copyJsonExpr(from);
+ break;
+ case T_JsonCommon:
+ retval = _copyJsonCommon(from);
+ break;
+ case T_JsonBehavior:
+ retval = _copyJsonBehavior(from);
+ break;
+ case T_JsonArgument:
+ retval = _copyJsonArgument(from);
+ break;
+ case T_JsonCoercion:
+ retval = _copyJsonCoercion(from);
+ break;
+ case T_JsonItemCoercions:
+ retval = _copyJsonItemCoercions(from);
+ break;
/*
* RELATION NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 800f588b5c..ffb186825f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -841,6 +841,242 @@ _equalOnConflictExpr(const OnConflictExpr *a, const OnConflictExpr *b)
return true;
}
+static bool
+_equalJsonFormat(const JsonFormat *a, const JsonFormat *b)
+{
+ COMPARE_SCALAR_FIELD(format);
+ COMPARE_SCALAR_FIELD(encoding);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalJsonReturning(const JsonReturning *a, const JsonReturning *b)
+{
+ COMPARE_NODE_FIELD(format);
+ COMPARE_SCALAR_FIELD(typid);
+ COMPARE_SCALAR_FIELD(typmod);
+
+ return true;
+}
+
+static bool
+_equalJsonValueExpr(const JsonValueExpr *a, const JsonValueExpr *b)
+{
+ COMPARE_NODE_FIELD(raw_expr);
+ COMPARE_NODE_FIELD(formatted_expr);
+ COMPARE_NODE_FIELD(format);
+
+ return true;
+}
+
+static bool
+_equalJsonScalarExpr(const JsonScalarExpr *a, const JsonScalarExpr *b)
+{
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalJsonSerializeExpr(const JsonSerializeExpr *a, const JsonSerializeExpr *b)
+{
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(output);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalJsonConstructorExpr(const JsonConstructorExpr *a, const JsonConstructorExpr *b)
+{
+ COMPARE_SCALAR_FIELD(type);
+ COMPARE_NODE_FIELD(args);
+ COMPARE_NODE_FIELD(func);
+ COMPARE_NODE_FIELD(coercion);
+ COMPARE_NODE_FIELD(returning);
+ COMPARE_SCALAR_FIELD(absent_on_null);
+ COMPARE_SCALAR_FIELD(unique);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalJsonKeyValue(const JsonKeyValue *a, const JsonKeyValue *b)
+{
+ COMPARE_NODE_FIELD(key);
+ COMPARE_NODE_FIELD(value);
+
+ return true;
+}
+
+static bool
+_equalJsonObjectConstructor(const JsonObjectConstructor *a,
+ const JsonObjectConstructor *b)
+{
+ COMPARE_NODE_FIELD(exprs);
+ COMPARE_NODE_FIELD(output);
+ COMPARE_SCALAR_FIELD(absent_on_null);
+ COMPARE_SCALAR_FIELD(unique);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalJsonAggConstructor(const JsonAggConstructor *a,
+ const JsonAggConstructor *b)
+{
+ COMPARE_NODE_FIELD(output);
+ COMPARE_NODE_FIELD(agg_filter);
+ COMPARE_NODE_FIELD(agg_order);
+ COMPARE_NODE_FIELD(over);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalJsonObjectAgg(const JsonObjectAgg *a, const JsonObjectAgg *b)
+{
+ COMPARE_NODE_FIELD(constructor);
+ COMPARE_NODE_FIELD(arg);
+ COMPARE_SCALAR_FIELD(absent_on_null);
+ COMPARE_SCALAR_FIELD(unique);
+
+ return true;
+}
+
+static bool
+_equalJsonOutput(const JsonOutput *a, const JsonOutput *b)
+{
+ COMPARE_NODE_FIELD(typeName);
+ COMPARE_NODE_FIELD(returning);
+
+ return true;
+}
+
+static bool
+_equalJsonArrayConstructor(const JsonArrayConstructor *a,
+ const JsonArrayConstructor *b)
+{
+ COMPARE_NODE_FIELD(exprs);
+ COMPARE_NODE_FIELD(output);
+ COMPARE_SCALAR_FIELD(absent_on_null);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalJsonArrayAgg(const JsonArrayAgg *a, const JsonArrayAgg *b)
+{
+ COMPARE_NODE_FIELD(constructor);
+ COMPARE_NODE_FIELD(arg);
+ COMPARE_SCALAR_FIELD(absent_on_null);
+
+ return true;
+}
+
+static bool
+_equalJsonArrayQueryConstructor(const JsonArrayQueryConstructor *a,
+ const JsonArrayQueryConstructor *b)
+{
+ COMPARE_NODE_FIELD(query);
+ COMPARE_NODE_FIELD(output);
+ COMPARE_NODE_FIELD(format);
+ COMPARE_SCALAR_FIELD(absent_on_null);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalJsonIsPredicate(const JsonIsPredicate *a,
+ const JsonIsPredicate *b)
+{
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_SCALAR_FIELD(value_type);
+ COMPARE_SCALAR_FIELD(unique_keys);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+/*
+ * _equalJsonBehavior
+ */
+static bool
+_equalJsonBehavior(const JsonBehavior *a, const JsonBehavior *b)
+{
+ COMPARE_SCALAR_FIELD(btype);
+ COMPARE_NODE_FIELD(default_expr);
+
+ return true;
+}
+
+/*
+ * _equalJsonExpr
+ */
+static bool
+_equalJsonExpr(const JsonExpr *a, const JsonExpr *b)
+{
+ COMPARE_SCALAR_FIELD(op);
+ COMPARE_NODE_FIELD(formatted_expr);
+ COMPARE_NODE_FIELD(result_coercion);
+ COMPARE_NODE_FIELD(format);
+ COMPARE_NODE_FIELD(path_spec);
+ COMPARE_NODE_FIELD(passing_values);
+ COMPARE_NODE_FIELD(passing_names);
+ COMPARE_NODE_FIELD(returning);
+ COMPARE_NODE_FIELD(on_error);
+ COMPARE_NODE_FIELD(on_empty);
+ COMPARE_NODE_FIELD(coercions);
+ COMPARE_SCALAR_FIELD(wrapper);
+ COMPARE_SCALAR_FIELD(omit_quotes);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+/*
+ * _equalJsonCoercion
+ */
+static bool
+_equalJsonCoercion(const JsonCoercion *a, const JsonCoercion *b)
+{
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_SCALAR_FIELD(via_populate);
+ COMPARE_SCALAR_FIELD(via_io);
+ COMPARE_SCALAR_FIELD(collation);
+
+ return true;
+}
+
+/*
+ * _equalJsonItemCoercions
+ */
+static bool
+_equalJsonItemCoercions(const JsonItemCoercions *a, const JsonItemCoercions *b)
+{
+ COMPARE_NODE_FIELD(null);
+ COMPARE_NODE_FIELD(string);
+ COMPARE_NODE_FIELD(numeric);
+ COMPARE_NODE_FIELD(boolean);
+ COMPARE_NODE_FIELD(date);
+ COMPARE_NODE_FIELD(time);
+ COMPARE_NODE_FIELD(timetz);
+ COMPARE_NODE_FIELD(timestamp);
+ COMPARE_NODE_FIELD(timestamptz);
+ COMPARE_NODE_FIELD(composite);
+
+ return true;
+}
+
/*
* Stuff from pathnodes.h
*/
@@ -3326,6 +3562,39 @@ equal(const void *a, const void *b)
case T_JoinExpr:
retval = _equalJoinExpr(a, b);
break;
+ case T_JsonFormat:
+ retval = _equalJsonFormat(a, b);
+ break;
+ case T_JsonReturning:
+ retval = _equalJsonReturning(a, b);
+ break;
+ case T_JsonValueExpr:
+ retval = _equalJsonValueExpr(a, b);
+ break;
+ case T_JsonScalarExpr:
+ retval = _equalJsonScalarExpr(a, b);
+ break;
+ case T_JsonSerializeExpr:
+ retval = _equalJsonSerializeExpr(a, b);
+ break;
+ case T_JsonConstructorExpr:
+ retval = _equalJsonConstructorExpr(a, b);
+ break;
+ case T_JsonIsPredicate:
+ retval = _equalJsonIsPredicate(a, b);
+ break;
+ case T_JsonBehavior:
+ retval = _equalJsonBehavior(a, b);
+ break;
+ case T_JsonExpr:
+ retval = _equalJsonExpr(a, b);
+ break;
+ case T_JsonCoercion:
+ retval = _equalJsonCoercion(a, b);
+ break;
+ case T_JsonItemCoercions:
+ retval = _equalJsonItemCoercions(a, b);
+ break;
/*
* RELATION NODES
@@ -3897,6 +4166,30 @@ equal(const void *a, const void *b)
case T_PublicationTable:
retval = _equalPublicationTable(a, b);
break;
+ case T_JsonKeyValue:
+ retval = _equalJsonKeyValue(a, b);
+ break;
+ case T_JsonObjectConstructor:
+ retval = _equalJsonObjectConstructor(a, b);
+ break;
+ case T_JsonAggConstructor:
+ retval = _equalJsonAggConstructor(a, b);
+ break;
+ case T_JsonObjectAgg:
+ retval = _equalJsonObjectAgg(a, b);
+ break;
+ case T_JsonOutput:
+ retval = _equalJsonOutput(a, b);
+ break;
+ case T_JsonArrayConstructor:
+ retval = _equalJsonArrayConstructor(a, b);
+ break;
+ case T_JsonArrayQueryConstructor:
+ retval = _equalJsonArrayQueryConstructor(a, b);
+ break;
+ case T_JsonArrayAgg:
+ retval = _equalJsonArrayAgg(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 7d1a01d1ed..928570b22e 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -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"
@@ -815,3 +816,105 @@ 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;
+ 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;
+}
+
+/*
+ * makeJsonBehavior -
+ * creates a JsonBehavior node
+ */
+JsonBehavior *
+makeJsonBehavior(JsonBehaviorType type, Node *default_expr)
+{
+ JsonBehavior *behavior = makeNode(JsonBehavior);
+
+ behavior->btype = type;
+ behavior->default_expr = default_expr;
+
+ return behavior;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * makeJsonIsPredicate -
+ * creates a JsonIsPredicate node
+ */
+Node *
+makeJsonIsPredicate(Node *expr, JsonFormat *format, JsonValueType value_type,
+ bool unique_keys, int location)
+{
+ JsonIsPredicate *n = makeNode(JsonIsPredicate);
+
+ n->expr = expr;
+ n->format = format;
+ n->value_type = value_type;
+ n->unique_keys = unique_keys;
+ n->location = location;
+
+ return (Node *) n;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index e276264882..97bd0bac6b 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -250,6 +250,25 @@ 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;
+ case T_JsonIsPredicate:
+ type = BOOLOID;
+ break;
+ case T_JsonExpr:
+ type = ((const JsonExpr *) expr)->returning->typid;
+ break;
+ case T_JsonCoercion:
+ type = exprType(((const JsonCoercion *) expr)->expr);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -482,6 +501,14 @@ 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 ((const JsonConstructorExpr *) expr)->returning->typmod;
+ case T_JsonExpr:
+ return ((JsonExpr *) expr)->returning->typmod;
+ case T_JsonCoercion:
+ return exprTypmod(((const JsonCoercion *) expr)->expr);
default:
break;
}
@@ -956,6 +983,37 @@ 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;
+ case T_JsonIsPredicate:
+ coll = InvalidOid; /* result is always an boolean type */
+ break;
+ case T_JsonExpr:
+ {
+ JsonExpr *jexpr = (JsonExpr *) expr;
+ JsonCoercion *coercion = jexpr->result_coercion;
+
+ if (!coercion)
+ coll = InvalidOid;
+ else if (coercion->expr)
+ coll = exprCollation(coercion->expr);
+ else if (coercion->via_io || coercion->via_populate)
+ coll = coercion->collation;
+ else
+ coll = InvalidOid;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
@@ -1168,6 +1226,38 @@ 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;
+ case T_JsonIsPredicate:
+ Assert(!OidIsValid(collation)); /* result is always boolean */
+ break;
+ case T_JsonExpr:
+ {
+ JsonExpr *jexpr = (JsonExpr *) expr;
+ JsonCoercion *coercion = jexpr->result_coercion;
+
+ if (!coercion)
+ Assert(!OidIsValid(collation));
+ else if (coercion->expr)
+ exprSetCollation(coercion->expr, collation);
+ else if (coercion->via_io || coercion->via_populate)
+ coercion->collation = collation;
+ else
+ Assert(!OidIsValid(collation));
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1614,6 +1704,24 @@ 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;
+ case T_JsonIsPredicate:
+ loc = ((const JsonIsPredicate *) expr)->location;
+ break;
+ case T_JsonExpr:
+ {
+ const JsonExpr *jsexpr = (const JsonExpr *) expr;
+
+ /* consider both function name and leftmost arg */
+ loc = leftmostLoc(jsexpr->location,
+ exprLocation(jsexpr->formatted_expr));
+ }
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2328,6 +2436,78 @@ expression_tree_walker(Node *node,
return true;
}
break;
+ case T_JsonValueExpr:
+ {
+ JsonValueExpr *jve = (JsonValueExpr *) node;
+
+ if (walker(jve->raw_expr, context))
+ return true;
+ if (walker(jve->formatted_expr, context))
+ return true;
+ }
+ break;
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+
+ if (walker(ctor->args, context))
+ return true;
+ if (walker(ctor->func, context))
+ return true;
+ if (walker(ctor->coercion, context))
+ return true;
+ }
+ break;
+ case T_JsonIsPredicate:
+ return walker(((JsonIsPredicate *) node)->expr, context);
+ case T_JsonExpr:
+ {
+ JsonExpr *jexpr = (JsonExpr *) node;
+
+ if (walker(jexpr->formatted_expr, context))
+ return true;
+ if (walker(jexpr->result_coercion, context))
+ return true;
+ if (walker(jexpr->passing_values, context))
+ return true;
+ /* we assume walker doesn't care about passing_names */
+ if (jexpr->on_empty &&
+ walker(jexpr->on_empty->default_expr, context))
+ return true;
+ if (walker(jexpr->on_error->default_expr, context))
+ return true;
+ if (walker(jexpr->coercions, context))
+ return true;
+ }
+ break;
+ case T_JsonCoercion:
+ return walker(((JsonCoercion *) node)->expr, context);
+ case T_JsonItemCoercions:
+ {
+ JsonItemCoercions *coercions = (JsonItemCoercions *) node;
+
+ if (walker(coercions->null, context))
+ return true;
+ if (walker(coercions->string, context))
+ return true;
+ if (walker(coercions->numeric, context))
+ return true;
+ if (walker(coercions->boolean, context))
+ return true;
+ if (walker(coercions->date, context))
+ return true;
+ if (walker(coercions->time, context))
+ return true;
+ if (walker(coercions->timetz, context))
+ return true;
+ if (walker(coercions->timestamp, context))
+ return true;
+ if (walker(coercions->timestamptz, context))
+ return true;
+ if (walker(coercions->composite, context))
+ return true;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -2658,6 +2838,7 @@ expression_tree_mutator(Node *node,
case T_RangeTblRef:
case T_SortGroupClause:
case T_CTESearchClause:
+ case T_JsonFormat:
return (Node *) copyObject(node);
case T_WithCheckOption:
{
@@ -3267,6 +3448,101 @@ expression_tree_mutator(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;
+ }
+ break;
+ case T_JsonIsPredicate:
+ {
+ JsonIsPredicate *pred = (JsonIsPredicate *) node;
+ JsonIsPredicate *newnode;
+
+ FLATCOPY(newnode, pred, JsonIsPredicate);
+ MUTATE(newnode->expr, pred->expr, Node *);
+
+ return (Node *) newnode;
+ }
+ break;
+ case T_JsonExpr:
+ {
+ JsonExpr *jexpr = (JsonExpr *) node;
+ JsonExpr *newnode;
+
+ FLATCOPY(newnode, jexpr, JsonExpr);
+ MUTATE(newnode->path_spec, jexpr->path_spec, Node *);
+ MUTATE(newnode->formatted_expr, jexpr->formatted_expr, Node *);
+ MUTATE(newnode->result_coercion, jexpr->result_coercion, JsonCoercion *);
+ MUTATE(newnode->passing_values, jexpr->passing_values, List *);
+ /* assume mutator does not care about passing_names */
+ if (newnode->on_empty)
+ MUTATE(newnode->on_empty->default_expr,
+ jexpr->on_empty->default_expr, Node *);
+ MUTATE(newnode->on_error->default_expr,
+ jexpr->on_error->default_expr, Node *);
+ return (Node *) newnode;
+ }
+ break;
+ case T_JsonCoercion:
+ {
+ JsonCoercion *coercion = (JsonCoercion *) node;
+ JsonCoercion *newnode;
+
+ FLATCOPY(newnode, coercion, JsonCoercion);
+ MUTATE(newnode->expr, coercion->expr, Node *);
+ return (Node *) newnode;
+ }
+ break;
+ case T_JsonItemCoercions:
+ {
+ JsonItemCoercions *coercions = (JsonItemCoercions *) node;
+ JsonItemCoercions *newnode;
+
+ FLATCOPY(newnode, coercions, JsonItemCoercions);
+ MUTATE(newnode->null, coercions->null, JsonCoercion *);
+ MUTATE(newnode->string, coercions->string, JsonCoercion *);
+ MUTATE(newnode->numeric, coercions->numeric, JsonCoercion *);
+ MUTATE(newnode->boolean, coercions->boolean, JsonCoercion *);
+ MUTATE(newnode->date, coercions->date, JsonCoercion *);
+ MUTATE(newnode->time, coercions->time, JsonCoercion *);
+ MUTATE(newnode->timetz, coercions->timetz, JsonCoercion *);
+ MUTATE(newnode->timestamp, coercions->timestamp, JsonCoercion *);
+ MUTATE(newnode->timestamptz, coercions->timestamptz, JsonCoercion *);
+ MUTATE(newnode->composite, coercions->composite, JsonCoercion *);
+ return (Node *) newnode;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -3974,6 +4250,157 @@ raw_expression_tree_walker(Node *node,
case T_CommonTableExpr:
/* search_clause and cycle_clause are not interesting here */
return walker(((CommonTableExpr *) node)->ctequery, context);
+ case T_JsonReturning:
+ return walker(((JsonReturning *) node)->format, context);
+ case T_JsonValueExpr:
+ {
+ JsonValueExpr *jve = (JsonValueExpr *) node;
+
+ if (walker(jve->raw_expr, context))
+ return true;
+ if (walker(jve->formatted_expr, context))
+ return true;
+ if (walker(jve->format, context))
+ return true;
+ }
+ break;
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+
+ if (walker(ctor->args, context))
+ return true;
+ if (walker(ctor->func, context))
+ return true;
+ if (walker(ctor->coercion, context))
+ return true;
+ if (walker(ctor->returning, context))
+ return true;
+ }
+ break;
+ case T_JsonOutput:
+ {
+ JsonOutput *out = (JsonOutput *) node;
+
+ if (walker(out->typeName, context))
+ return true;
+ if (walker(out->returning, context))
+ return true;
+ }
+ break;
+ case T_JsonKeyValue:
+ {
+ JsonKeyValue *jkv = (JsonKeyValue *) node;
+
+ if (walker(jkv->key, context))
+ return true;
+ if (walker(jkv->value, context))
+ return true;
+ }
+ break;
+ case T_JsonObjectConstructor:
+ {
+ JsonObjectConstructor *joc = (JsonObjectConstructor *) node;
+
+ if (walker(joc->output, context))
+ return true;
+ if (walker(joc->exprs, context))
+ return true;
+ }
+ break;
+ case T_JsonArrayConstructor:
+ {
+ JsonArrayConstructor *jac = (JsonArrayConstructor *) node;
+
+ if (walker(jac->output, context))
+ return true;
+ if (walker(jac->exprs, context))
+ return true;
+ }
+ break;
+ case T_JsonAggConstructor:
+ {
+ JsonAggConstructor *ctor = (JsonAggConstructor *) node;
+
+ if (walker(ctor->output, context))
+ return true;
+ if (walker(ctor->agg_order, context))
+ return true;
+ if (walker(ctor->agg_filter, context))
+ return true;
+ if (walker(ctor->over, context))
+ return true;
+ }
+ break;
+ case T_JsonObjectAgg:
+ {
+ JsonObjectAgg *joa = (JsonObjectAgg *) node;
+
+ if (walker(joa->constructor, context))
+ return true;
+ if (walker(joa->arg, context))
+ return true;
+ }
+ break;
+ case T_JsonArrayAgg:
+ {
+ JsonArrayAgg *jaa = (JsonArrayAgg *) node;
+
+ if (walker(jaa->constructor, context))
+ return true;
+ if (walker(jaa->arg, context))
+ return true;
+ }
+ break;
+ case T_JsonArrayQueryConstructor:
+ {
+ JsonArrayQueryConstructor *jaqc = (JsonArrayQueryConstructor *) node;
+
+ if (walker(jaqc->output, context))
+ return true;
+ if (walker(jaqc->query, context))
+ return true;
+ }
+ break;
+ case T_JsonIsPredicate:
+ return walker(((JsonIsPredicate *) node)->expr, context);
+ case T_JsonArgument:
+ return walker(((JsonArgument *) node)->val, context);
+ case T_JsonCommon:
+ {
+ JsonCommon *jc = (JsonCommon *) node;
+
+ if (walker(jc->expr, context))
+ return true;
+ if (walker(jc->pathspec, context))
+ return true;
+ if (walker(jc->passing, context))
+ return true;
+ }
+ break;
+ case T_JsonBehavior:
+ {
+ JsonBehavior *jb = (JsonBehavior *) node;
+
+ if (jb->btype == JSON_BEHAVIOR_DEFAULT &&
+ walker(jb->default_expr, context))
+ return true;
+ }
+ break;
+ case T_JsonFuncExpr:
+ {
+ JsonFuncExpr *jfe = (JsonFuncExpr *) node;
+
+ if (walker(jfe->common, context))
+ return true;
+ if (jfe->output && walker(jfe->output, context))
+ return true;
+ if (walker(jfe->on_empty, context))
+ return true;
+ if (walker(jfe->on_error, context))
+ return true;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2e5ed77e18..8012904870 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1748,6 +1748,120 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
WRITE_NODE_FIELD(exclRelTlist);
}
+static void
+_outJsonFormat(StringInfo str, const JsonFormat *node)
+{
+ WRITE_NODE_TYPE("JSONFORMAT");
+
+ WRITE_ENUM_FIELD(format, JsonFormatType);
+ WRITE_ENUM_FIELD(encoding, JsonEncoding);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outJsonReturning(StringInfo str, const JsonReturning *node)
+{
+ WRITE_NODE_TYPE("JSONRETURNING");
+
+ WRITE_NODE_FIELD(format);
+ WRITE_OID_FIELD(typid);
+ WRITE_INT_FIELD(typmod);
+}
+
+static void
+_outJsonValueExpr(StringInfo str, const JsonValueExpr *node)
+{
+ WRITE_NODE_TYPE("JSONVALUEEXPR");
+
+ WRITE_NODE_FIELD(raw_expr);
+ WRITE_NODE_FIELD(formatted_expr);
+ WRITE_NODE_FIELD(format);
+}
+
+static void
+_outJsonConstructorExpr(StringInfo str, const JsonConstructorExpr *node)
+{
+ WRITE_NODE_TYPE("JSONCTOREXPR");
+
+ WRITE_NODE_FIELD(args);
+ WRITE_NODE_FIELD(func);
+ WRITE_NODE_FIELD(coercion);
+ WRITE_INT_FIELD(type);
+ WRITE_NODE_FIELD(returning);
+ WRITE_BOOL_FIELD(unique);
+ WRITE_BOOL_FIELD(absent_on_null);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outJsonIsPredicate(StringInfo str, const JsonIsPredicate *node)
+{
+ WRITE_NODE_TYPE("JSONISPREDICATE");
+
+ WRITE_NODE_FIELD(expr);
+ WRITE_ENUM_FIELD(value_type, JsonValueType);
+ WRITE_BOOL_FIELD(unique_keys);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outJsonBehavior(StringInfo str, const JsonBehavior *node)
+{
+ WRITE_NODE_TYPE("JSONBEHAVIOR");
+
+ WRITE_ENUM_FIELD(btype, JsonBehaviorType);
+ WRITE_NODE_FIELD(default_expr);
+}
+
+static void
+_outJsonExpr(StringInfo str, const JsonExpr *node)
+{
+ WRITE_NODE_TYPE("JSONEXPR");
+
+ WRITE_ENUM_FIELD(op, JsonExprOp);
+ WRITE_NODE_FIELD(formatted_expr);
+ WRITE_NODE_FIELD(result_coercion);
+ WRITE_NODE_FIELD(format);
+ WRITE_NODE_FIELD(path_spec);
+ WRITE_NODE_FIELD(passing_values);
+ WRITE_NODE_FIELD(passing_names);
+ WRITE_NODE_FIELD(returning);
+ WRITE_NODE_FIELD(on_error);
+ WRITE_NODE_FIELD(on_empty);
+ WRITE_NODE_FIELD(coercions);
+ WRITE_ENUM_FIELD(wrapper, JsonWrapper);
+ WRITE_BOOL_FIELD(omit_quotes);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outJsonCoercion(StringInfo str, const JsonCoercion *node)
+{
+ WRITE_NODE_TYPE("JSONCOERCION");
+
+ WRITE_NODE_FIELD(expr);
+ WRITE_BOOL_FIELD(via_populate);
+ WRITE_BOOL_FIELD(via_io);
+ WRITE_OID_FIELD(collation);
+}
+
+static void
+_outJsonItemCoercions(StringInfo str, const JsonItemCoercions *node)
+{
+ WRITE_NODE_TYPE("JSONITEMCOERCIONS");
+
+ WRITE_NODE_FIELD(null);
+ WRITE_NODE_FIELD(string);
+ WRITE_NODE_FIELD(numeric);
+ WRITE_NODE_FIELD(boolean);
+ WRITE_NODE_FIELD(date);
+ WRITE_NODE_FIELD(time);
+ WRITE_NODE_FIELD(timetz);
+ WRITE_NODE_FIELD(timestamp);
+ WRITE_NODE_FIELD(timestamptz);
+ WRITE_NODE_FIELD(composite);
+}
+
/*****************************************************************************
*
* Stuff from pathnodes.h.
@@ -4521,6 +4635,33 @@ outNode(StringInfo str, const void *obj)
case T_PartitionRangeDatum:
_outPartitionRangeDatum(str, obj);
break;
+ case T_JsonFormat:
+ _outJsonFormat(str, obj);
+ break;
+ case T_JsonReturning:
+ _outJsonReturning(str, obj);
+ break;
+ case T_JsonValueExpr:
+ _outJsonValueExpr(str, obj);
+ break;
+ case T_JsonConstructorExpr:
+ _outJsonConstructorExpr(str, obj);
+ break;
+ case T_JsonIsPredicate:
+ _outJsonIsPredicate(str, obj);
+ break;
+ case T_JsonBehavior:
+ _outJsonBehavior(str, obj);
+ break;
+ case T_JsonExpr:
+ _outJsonExpr(str, obj);
+ break;
+ case T_JsonCoercion:
+ _outJsonCoercion(str, obj);
+ break;
+ case T_JsonItemCoercions:
+ _outJsonItemCoercions(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index abf08b7a2f..2eee045db0 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1391,6 +1391,165 @@ _readOnConflictExpr(void)
READ_DONE();
}
+/*
+ * _readJsonFormat
+ */
+static JsonFormat *
+_readJsonFormat(void)
+{
+ READ_LOCALS(JsonFormat);
+
+ READ_ENUM_FIELD(format, JsonFormatType);
+ READ_ENUM_FIELD(encoding, JsonEncoding);
+ READ_LOCATION_FIELD(location);
+
+ READ_DONE();
+}
+
+/*
+ * _readJsonReturning
+ */
+static JsonReturning *
+_readJsonReturning(void)
+{
+ READ_LOCALS(JsonReturning);
+
+ READ_NODE_FIELD(format);
+ READ_OID_FIELD(typid);
+ READ_INT_FIELD(typmod);
+
+ READ_DONE();
+}
+
+/*
+ * _readJsonValueExpr
+ */
+static JsonValueExpr *
+_readJsonValueExpr(void)
+{
+ READ_LOCALS(JsonValueExpr);
+
+ READ_NODE_FIELD(raw_expr);
+ READ_NODE_FIELD(formatted_expr);
+ READ_NODE_FIELD(format);
+
+ READ_DONE();
+}
+
+/*
+ * _readJsonConstructorExpr
+ */
+static JsonConstructorExpr *
+_readJsonConstructorExpr(void)
+{
+ READ_LOCALS(JsonConstructorExpr);
+
+ READ_NODE_FIELD(args);
+ READ_NODE_FIELD(func);
+ READ_NODE_FIELD(coercion);
+ READ_INT_FIELD(type);
+ READ_NODE_FIELD(returning);
+ READ_BOOL_FIELD(unique);
+ READ_BOOL_FIELD(absent_on_null);
+ READ_LOCATION_FIELD(location);
+
+ READ_DONE();
+}
+
+/*
+ * _readJsonBehavior
+ */
+static JsonBehavior *
+_readJsonBehavior(void)
+{
+ READ_LOCALS(JsonBehavior);
+
+ READ_ENUM_FIELD(btype, JsonBehaviorType);
+ READ_NODE_FIELD(default_expr);
+
+ READ_DONE();
+}
+
+/*
+ * _readJsonExpr
+ */
+static JsonExpr *
+_readJsonExpr(void)
+{
+ READ_LOCALS(JsonExpr);
+
+ READ_ENUM_FIELD(op, JsonExprOp);
+ READ_NODE_FIELD(formatted_expr);
+ READ_NODE_FIELD(result_coercion);
+ READ_NODE_FIELD(format);
+ READ_NODE_FIELD(path_spec);
+ READ_NODE_FIELD(passing_values);
+ READ_NODE_FIELD(passing_names);
+ READ_NODE_FIELD(returning);
+ READ_NODE_FIELD(on_error);
+ READ_NODE_FIELD(on_empty);
+ READ_NODE_FIELD(coercions);
+ READ_ENUM_FIELD(wrapper, JsonWrapper);
+ READ_BOOL_FIELD(omit_quotes);
+ READ_LOCATION_FIELD(location);
+
+ READ_DONE();
+}
+
+/*
+ * _readJsonCoercion
+ */
+static JsonCoercion *
+_readJsonCoercion(void)
+{
+ READ_LOCALS(JsonCoercion);
+
+ READ_NODE_FIELD(expr);
+ READ_BOOL_FIELD(via_populate);
+ READ_BOOL_FIELD(via_io);
+ READ_OID_FIELD(collation);
+
+ READ_DONE();
+}
+
+/*
+ * _readJsonItemCoercions
+ */
+static JsonItemCoercions *
+_readJsonItemCoercions(void)
+{
+ READ_LOCALS(JsonItemCoercions);
+
+ READ_NODE_FIELD(null);
+ READ_NODE_FIELD(string);
+ READ_NODE_FIELD(numeric);
+ READ_NODE_FIELD(boolean);
+ READ_NODE_FIELD(date);
+ READ_NODE_FIELD(time);
+ READ_NODE_FIELD(timetz);
+ READ_NODE_FIELD(timestamp);
+ READ_NODE_FIELD(timestamptz);
+ READ_NODE_FIELD(composite);
+
+ READ_DONE();
+}
+
+/*
+ * _readJsonIsPredicate
+ */
+static JsonIsPredicate *
+_readJsonIsPredicate()
+{
+ READ_LOCALS(JsonIsPredicate);
+
+ READ_NODE_FIELD(expr);
+ READ_ENUM_FIELD(value_type, JsonValueType);
+ READ_BOOL_FIELD(unique_keys);
+ READ_LOCATION_FIELD(location);
+
+ READ_DONE();
+}
+
/*
* Stuff from pathnodes.h.
*
@@ -2973,6 +3132,24 @@ parseNodeString(void)
return_value = _readPartitionBoundSpec();
else if (MATCH("PARTITIONRANGEDATUM", 19))
return_value = _readPartitionRangeDatum();
+ else if (MATCH("JSONFORMAT", 10))
+ return_value = _readJsonFormat();
+ else if (MATCH("JSONRETURNING", 13))
+ return_value = _readJsonReturning();
+ else if (MATCH("JSONVALUEEXPR", 13))
+ return_value = _readJsonValueExpr();
+ else if (MATCH("JSONCTOREXPR", 12))
+ return_value = _readJsonConstructorExpr();
+ else if (MATCH("JSONISPREDICATE", 15))
+ return_value = _readJsonIsPredicate();
+ else if (MATCH("JSONBEHAVIOR", 12))
+ return_value = _readJsonBehavior();
+ else if (MATCH("JSONEXPR", 8))
+ return_value = _readJsonExpr();
+ else if (MATCH("JSONCOERCION", 12))
+ return_value = _readJsonCoercion();
+ else if (MATCH("JSONITEMCOERCIONS", 17))
+ return_value = _readJsonItemCoercions();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1e4d404f02..5033e8c87a 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -4540,7 +4540,8 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
IsA(node, SQLValueFunction) ||
IsA(node, XmlExpr) ||
IsA(node, CoerceToDomain) ||
- IsA(node, NextValueExpr))
+ IsA(node, NextValueExpr) ||
+ IsA(node, JsonExpr))
{
/* Treat all these as having cost 1 */
context->total.per_tuple += cpu_operator_cost;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 3412d31117..c1951c1caf 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -28,6 +28,7 @@
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/functions.h"
+#include "executor/execExpr.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -50,6 +51,9 @@
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
+#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
@@ -381,6 +385,45 @@ contain_mutable_functions_walker(Node *node, void *context)
context))
return true;
+ if (IsA(node, JsonConstructorExpr))
+ {
+ JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+ ListCell *lc;
+ bool is_jsonb =
+ ctor->returning->format->format == JS_FORMAT_JSONB;
+
+ /* Check argument_type => json[b] conversions */
+ 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, JsonExpr))
+ {
+ JsonExpr *jexpr = castNode(JsonExpr, node);
+ Const *cnst;
+
+ if (!IsA(jexpr->path_spec, Const))
+ return true;
+
+ cnst = castNode(Const, jexpr->path_spec);
+
+ Assert(cnst->consttype == JSONPATHOID);
+ if (cnst->constisnull)
+ return false;
+
+ return jspIsMutable(DatumGetJsonPathP(cnst->constvalue),
+ jexpr->passing_names, jexpr->passing_values);
+ }
+
if (IsA(node, SQLValueFunction))
{
/* all variants of SQLValueFunction are stable */
@@ -852,6 +895,18 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context, 0);
}
+ /* JsonExpr is parallel-unsafe if subtransactions can be used. */
+ else if (IsA(node, JsonExpr))
+ {
+ JsonExpr *jsexpr = (JsonExpr *) node;
+
+ if (ExecEvalJsonNeedsSubTransaction(jsexpr, NULL))
+ {
+ context->max_hazard = PROPARALLEL_UNSAFE;
+ return true;
+ }
+ }
+
/* Recurse to check arguments */
return expression_tree_walker(node,
max_parallel_hazard_walker,
@@ -3511,6 +3566,29 @@ eval_const_expressions_mutator(Node *node,
return ece_evaluate_expr((Node *) newcre);
return (Node *) newcre;
}
+ case T_JsonValueExpr:
+ {
+ JsonValueExpr *jve = (JsonValueExpr *) node;
+ Node *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;
+ }
default:
break;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..b70949b122 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -62,6 +62,7 @@
#include "storage/lmgr.h"
#include "utils/date.h"
#include "utils/datetime.h"
+#include "utils/jsonb.h"
#include "utils/numeric.h"
#include "utils/xml.h"
@@ -259,6 +260,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
+ JsonBehavior *jsbehavior;
+ struct
+ {
+ JsonBehavior *on_empty;
+ JsonBehavior *on_error;
+ } on_behavior;
+ JsonQuotes js_quotes;
}
%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -534,7 +542,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> copy_options
%type <typnam> Typename SimpleTypename ConstTypename
- GenericType Numeric opt_float
+ GenericType Numeric opt_float JsonType
Character ConstCharacter
CharacterWithLength CharacterWithoutLength
ConstDatetime ConstInterval
@@ -610,6 +618,73 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> hash_partbound_elem
+%type <node> json_format_clause_opt
+ json_representation
+ json_value_expr
+ json_func_expr
+ json_value_func_expr
+ json_query_expr
+ json_exists_predicate
+ json_parse_expr
+ json_scalar_expr
+ json_serialize_expr
+ json_api_common_syntax
+ json_context_item
+ json_argument
+ json_output_clause_opt
+ json_returning_clause_opt
+ json_value_constructor
+ json_object_constructor
+ json_object_constructor_args
+ json_object_constructor_args_opt
+ json_object_args
+ json_object_func_args
+ json_array_constructor
+ json_name_and_value
+ json_aggregate_func
+ json_object_aggregate_constructor
+ json_array_aggregate_constructor
+ json_path_specification
+
+%type <list> json_name_and_value_list
+ json_value_expr_list
+ json_array_aggregate_order_by_clause_opt
+ json_arguments
+ json_passing_clause_opt
+
+%type <str> json_table_path_name
+ json_as_path_name_clause_opt
+
+%type <ival> json_encoding
+ json_encoding_clause_opt
+ json_wrapper_clause_opt
+ json_wrapper_behavior
+ json_conditional_or_unconditional_opt
+ json_predicate_type_constraint_opt
+
+%type <jsbehavior> json_behavior_error
+ json_behavior_null
+ json_behavior_true
+ json_behavior_false
+ json_behavior_unknown
+ json_behavior_empty_array
+ json_behavior_empty_object
+ json_behavior_default
+ json_value_behavior
+ json_query_behavior
+ json_exists_error_behavior
+ json_exists_error_clause_opt
+
+%type <on_behavior> json_value_on_behavior_clause_opt
+ json_query_on_behavior_clause_opt
+
+%type <js_quotes> json_quotes_behavior
+ json_quotes_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
@@ -635,7 +710,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
@@ -645,7 +720,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CACHE CALL CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT
- COMMITTED COMPRESSION CONCURRENTLY CONFIGURATION CONFLICT
+ COMMITTED COMPRESSION CONCURRENTLY CONDITIONAL CONFIGURATION CONFLICT
CONNECTION CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY
COST CREATE CROSS CSV CUBE CURRENT_P
CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
@@ -656,12 +731,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
- EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
- EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
+ EACH ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE
+ EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
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
@@ -672,9 +747,10 @@ 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_EXISTS JSON_OBJECT JSON_OBJECTAGG
+ JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_VALUE
- KEY
+ KEY KEYS KEEP
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
@@ -687,7 +763,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
NULLS_P NUMERIC
- OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR
+ OBJECT_P OF OFF OFFSET OIDS OLD OMIT ON ONLY OPERATOR OPTION OPTIONS OR
ORDER ORDINALITY OTHERS OUT_P OUTER_P
OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
@@ -695,17 +771,17 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
- QUOTE
+ QUOTE QUOTES
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
- SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
- SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
- SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
- START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
+ SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
+ SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF
+ SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
+ START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING STRIP_P
SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
@@ -713,7 +789,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TREAT TRIGGER TRIM TRUE_P
TRUNCATE TRUSTED TYPE_P TYPES_P
- UESCAPE UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN
+ UESCAPE UNBOUNDED UNCONDITIONAL UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN
UNLISTEN UNLOGGED UNTIL UPDATE USER USING
VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
@@ -738,7 +814,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* as NOT, at least with respect to their left-hand subexpression.
* NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
*/
-%token NOT_LA NULLS_LA WITH_LA
+%token NOT_LA NULLS_LA WITH_LA WITH_LA_UNIQUE WITHOUT_LA
/*
* The grammar likewise thinks these tokens are keywords, but they are never
@@ -756,6 +832,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
@@ -791,11 +868,15 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* Using the same precedence as IDENT seems right for the reasons given above.
*/
%nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */
+%nonassoc ERROR_P EMPTY_P DEFAULT ABSENT /* JSON error/empty behavior */
+%nonassoc FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN UNIQUE JSON
%nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
%left Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-'
%left '*' '/' '%'
%left '^'
+%left KEYS /* UNIQUE [ KEYS ] */
+%left OBJECT_P SCALAR VALUE_P /* JSON [ OBJECT | SCALAR | VALUE ] */
/* Unary Operators */
%left AT /* sets precedence for AT TIME ZONE */
%left COLLATE
@@ -813,6 +894,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
*/
%left JOIN CROSS LEFT FULL RIGHT INNER_P NATURAL
+%nonassoc empty_json_unique
+%left WITHOUT WITH_LA_UNIQUE
+
%%
/*
@@ -12850,6 +12934,7 @@ SimpleTypename:
$$->typmods = list_make2(makeIntConst(INTERVAL_FULL_RANGE, -1),
makeIntConst($3, @3));
}
+ | JsonType { $$ = $1; }
;
/* We have a separate ConstTypename to allow defaulting fixed-length
@@ -12868,6 +12953,7 @@ ConstTypename:
| ConstBit { $$ = $1; }
| ConstCharacter { $$ = $1; }
| ConstDatetime { $$ = $1; }
+ | JsonType { $$ = $1; }
;
/*
@@ -13161,7 +13247,7 @@ ConstInterval:
opt_timezone:
WITH_LA TIME ZONE { $$ = true; }
- | WITHOUT TIME ZONE { $$ = false; }
+ | WITHOUT_LA TIME ZONE { $$ = false; }
| /*EMPTY*/ { $$ = false; }
;
@@ -13236,6 +13322,14 @@ interval_second:
}
;
+/* Mapping of PG jsonb types to SQL/JSON JSON type */
+JsonType:
+ JSON
+ {
+ $$ = SystemTypeName(SQLJSON_TYPE_NAME());
+ $$->location = @1;
+ }
+ ;
/*****************************************************************************
*
@@ -13692,6 +13786,46 @@ a_expr: c_expr { $$ = $1; }
@2),
@2);
}
+ | a_expr
+ IS json_predicate_type_constraint_opt
+ json_key_uniqueness_constraint_opt %prec IS
+ {
+ JsonFormat *format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+ $$ = makeJsonIsPredicate($1, format, $3, $4, @1);
+ }
+ /*
+ * Required by standard, but it would conflict with expressions
+ * like: 'str' || format(...)
+ | a_expr
+ FORMAT json_representation
+ IS json_predicate_type_constraint_opt
+ json_key_uniqueness_constraint_opt %prec FORMAT
+ {
+ $3.location = @2;
+ $$ = makeJsonIsPredicate($1, $3, $5, $6, @1);
+ }
+ */
+ | a_expr
+ IS NOT
+ json_predicate_type_constraint_opt
+ json_key_uniqueness_constraint_opt %prec IS
+ {
+ JsonFormat *format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+ $$ = makeNotExpr(makeJsonIsPredicate($1, format, $4, $5, @1), @1);
+ }
+ /*
+ * Required by standard, but it would conflict with expressions
+ * like: 'str' || format(...)
+ | a_expr
+ FORMAT json_representation
+ IS NOT
+ json_predicate_type_constraint_opt
+ json_key_uniqueness_constraint_opt %prec FORMAT
+ {
+ $3.location = @2;
+ $$ = makeNotExpr(makeJsonIsPredicate($1, $3, $6, $7, @1), @1);
+ }
+ */
| DEFAULT
{
/*
@@ -13774,6 +13908,25 @@ b_expr: c_expr
}
;
+json_predicate_type_constraint_opt:
+ JSON { $$ = JS_TYPE_ANY; }
+ | JSON VALUE_P { $$ = JS_TYPE_ANY; }
+ | JSON ARRAY { $$ = JS_TYPE_ARRAY; }
+ | JSON OBJECT_P { $$ = JS_TYPE_OBJECT; }
+ | JSON SCALAR { $$ = JS_TYPE_SCALAR; }
+ ;
+
+json_key_uniqueness_constraint_opt:
+ WITH_LA_UNIQUE unique_keys { $$ = true; }
+ | WITHOUT unique_keys { $$ = false; }
+ | /* EMPTY */ %prec empty_json_unique { $$ = false; }
+ ;
+
+unique_keys:
+ UNIQUE
+ | UNIQUE KEYS
+ ;
+
/*
* Productions that can be used in both a_expr and b_expr.
*
@@ -14026,6 +14179,15 @@ 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; }
;
@@ -14039,6 +14201,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; }
;
/*
@@ -14326,6 +14489,8 @@ func_expr_common_subexpr:
n->location = @1;
$$ = (Node *)n;
}
+ | json_func_expr
+ { $$ = $1; }
;
/*
@@ -15024,6 +15189,530 @@ opt_asymmetric: ASYMMETRIC
| /*EMPTY*/
;
+/* SQL/JSON support */
+json_func_expr:
+ json_value_constructor
+ | json_value_func_expr
+ | json_query_expr
+ | json_exists_predicate
+ | json_parse_expr
+ | json_scalar_expr
+ | json_serialize_expr
+ ;
+
+json_parse_expr:
+ JSON '(' json_value_expr json_key_uniqueness_constraint_opt
+ json_returning_clause_opt ')'
+ {
+ JsonParseExpr *n = makeNode(JsonParseExpr);
+ n->expr = (JsonValueExpr *) $3;
+ n->unique_keys = $4;
+ n->output = (JsonOutput *) $5;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_scalar_expr:
+ JSON_SCALAR '(' a_expr json_returning_clause_opt ')'
+ {
+ JsonScalarExpr *n = makeNode(JsonScalarExpr);
+ n->expr = (Expr *) $3;
+ n->output = (JsonOutput *) $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_serialize_expr:
+ JSON_SERIALIZE '(' json_value_expr json_output_clause_opt ')'
+ {
+ JsonSerializeExpr *n = makeNode(JsonSerializeExpr);
+ n->expr = (JsonValueExpr *) $3;
+ n->output = (JsonOutput *) $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_value_func_expr:
+ JSON_VALUE '('
+ json_api_common_syntax
+ json_returning_clause_opt
+ json_value_on_behavior_clause_opt
+ ')'
+ {
+ JsonFuncExpr *n = makeNode(JsonFuncExpr);
+ n->op = IS_JSON_VALUE;
+ n->common = (JsonCommon *) $3;
+ n->output = (JsonOutput *) $4;
+ n->on_empty = $5.on_empty;
+ n->on_error = $5.on_error;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_api_common_syntax:
+ json_context_item ',' json_path_specification
+ json_as_path_name_clause_opt
+ json_passing_clause_opt
+ {
+ JsonCommon *n = makeNode(JsonCommon);
+ n->expr = (JsonValueExpr *) $1;
+ n->pathspec = $3;
+ n->pathname = $4;
+ n->passing = $5;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_context_item:
+ json_value_expr { $$ = $1; }
+ ;
+
+json_path_specification:
+ a_expr { $$ = $1; }
+ ;
+
+json_as_path_name_clause_opt:
+ AS json_table_path_name { $$ = $2; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+json_table_path_name:
+ name { $$ = $1; }
+ ;
+
+json_passing_clause_opt:
+ PASSING json_arguments { $$ = $2; }
+ | /* EMPTY */ { $$ = NIL; }
+ ;
+
+json_arguments:
+ json_argument { $$ = list_make1($1); }
+ | json_arguments ',' json_argument { $$ = lappend($1, $3); }
+ ;
+
+json_argument:
+ json_value_expr AS ColLabel
+ {
+ JsonArgument *n = makeNode(JsonArgument);
+ n->val = (JsonValueExpr *) $1;
+ n->name = $3;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_value_expr:
+ a_expr json_format_clause_opt
+ {
+ $$ = (Node *) makeJsonValueExpr((Expr *) $1, castNode(JsonFormat, $2));
+ }
+ ;
+
+json_format_clause_opt:
+ FORMAT json_representation
+ {
+ $$ = $2;
+ castNode(JsonFormat, $$)->location = @1;
+ }
+ | /* EMPTY */
+ {
+ $$ = (Node *) makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+ }
+ ;
+
+json_representation:
+ JSON json_encoding_clause_opt
+ {
+ $$ = (Node *) makeJsonFormat(JS_FORMAT_JSON, $2, @1);
+ }
+ /* | other implementation defined JSON representation options (BSON, AVRO etc) */
+ ;
+
+json_encoding_clause_opt:
+ ENCODING json_encoding { $$ = $2; }
+ | /* EMPTY */ { $$ = JS_ENC_DEFAULT; }
+ ;
+
+json_encoding:
+ name { $$ = makeJsonEncoding($1); }
+ ;
+
+json_behavior_error:
+ ERROR_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL); }
+ ;
+
+json_behavior_null:
+ NULL_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_NULL, NULL); }
+ ;
+
+json_behavior_true:
+ TRUE_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_TRUE, NULL); }
+ ;
+
+json_behavior_false:
+ FALSE_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_FALSE, NULL); }
+ ;
+
+json_behavior_unknown:
+ UNKNOWN { $$ = makeJsonBehavior(JSON_BEHAVIOR_UNKNOWN, NULL); }
+ ;
+
+json_behavior_empty_array:
+ EMPTY_P ARRAY { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); }
+ /* non-standard, for Oracle compatibility only */
+ | EMPTY_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); }
+ ;
+
+json_behavior_empty_object:
+ EMPTY_P OBJECT_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL); }
+ ;
+
+json_behavior_default:
+ DEFAULT a_expr { $$ = makeJsonBehavior(JSON_BEHAVIOR_DEFAULT, $2); }
+ ;
+
+
+json_value_behavior:
+ json_behavior_null
+ | json_behavior_error
+ | json_behavior_default
+ ;
+
+json_value_on_behavior_clause_opt:
+ json_value_behavior ON EMPTY_P
+ { $$.on_empty = $1; $$.on_error = NULL; }
+ | json_value_behavior ON EMPTY_P json_value_behavior ON ERROR_P
+ { $$.on_empty = $1; $$.on_error = $4; }
+ | json_value_behavior ON ERROR_P
+ { $$.on_empty = NULL; $$.on_error = $1; }
+ | /* EMPTY */
+ { $$.on_empty = NULL; $$.on_error = NULL; }
+ ;
+
+json_query_expr:
+ JSON_QUERY '('
+ json_api_common_syntax
+ json_output_clause_opt
+ json_wrapper_clause_opt
+ json_quotes_clause_opt
+ json_query_on_behavior_clause_opt
+ ')'
+ {
+ JsonFuncExpr *n = makeNode(JsonFuncExpr);
+ n->op = IS_JSON_QUERY;
+ n->common = (JsonCommon *) $3;
+ n->output = (JsonOutput *) $4;
+ n->wrapper = $5;
+ if (n->wrapper != JSW_NONE && $6 != JS_QUOTES_UNSPEC)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used"),
+ parser_errposition(@6)));
+ n->omit_quotes = $6 == JS_QUOTES_OMIT;
+ n->on_empty = $7.on_empty;
+ n->on_error = $7.on_error;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_wrapper_clause_opt:
+ json_wrapper_behavior WRAPPER { $$ = $1; }
+ | /* EMPTY */ { $$ = 0; }
+ ;
+
+json_wrapper_behavior:
+ WITHOUT array_opt { $$ = JSW_NONE; }
+ | WITH json_conditional_or_unconditional_opt array_opt { $$ = $2; }
+ ;
+
+array_opt:
+ ARRAY { }
+ | /* EMPTY */ { }
+ ;
+
+json_conditional_or_unconditional_opt:
+ CONDITIONAL { $$ = JSW_CONDITIONAL; }
+ | UNCONDITIONAL { $$ = JSW_UNCONDITIONAL; }
+ | /* EMPTY */ { $$ = JSW_UNCONDITIONAL; }
+ ;
+
+json_quotes_clause_opt:
+ json_quotes_behavior QUOTES json_on_scalar_string_opt { $$ = $1; }
+ | /* EMPTY */ { $$ = JS_QUOTES_UNSPEC; }
+ ;
+
+json_quotes_behavior:
+ KEEP { $$ = JS_QUOTES_KEEP; }
+ | OMIT { $$ = JS_QUOTES_OMIT; }
+ ;
+
+json_on_scalar_string_opt:
+ ON SCALAR STRING { }
+ | /* EMPTY */ { }
+ ;
+
+json_query_behavior:
+ json_behavior_error
+ | json_behavior_null
+ | json_behavior_empty_array
+ | json_behavior_empty_object
+ | json_behavior_default
+ ;
+
+json_query_on_behavior_clause_opt:
+ json_query_behavior ON EMPTY_P
+ { $$.on_empty = $1; $$.on_error = NULL; }
+ | json_query_behavior ON EMPTY_P json_query_behavior ON ERROR_P
+ { $$.on_empty = $1; $$.on_error = $4; }
+ | json_query_behavior ON ERROR_P
+ { $$.on_empty = NULL; $$.on_error = $1; }
+ | /* EMPTY */
+ { $$.on_empty = NULL; $$.on_error = NULL; }
+ ;
+
+json_returning_clause_opt:
+ RETURNING Typename
+ {
+ JsonOutput *n = makeNode(JsonOutput);
+ n->typeName = $2;
+ n->returning = makeNode(JsonReturning);
+ n->returning->format =
+ makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, @2);
+ $$ = (Node *) n;
+ }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+json_output_clause_opt:
+ RETURNING Typename json_format_clause_opt
+ {
+ JsonOutput *n = makeNode(JsonOutput);
+ n->typeName = $2;
+ n->returning = makeNode(JsonReturning);
+ n->returning->format = (JsonFormat *) $3;
+ $$ = (Node *) n;
+ }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+json_exists_predicate:
+ JSON_EXISTS '('
+ json_api_common_syntax
+ json_returning_clause_opt
+ json_exists_error_clause_opt
+ ')'
+ {
+ JsonFuncExpr *p = makeNode(JsonFuncExpr);
+ p->op = IS_JSON_EXISTS;
+ p->common = (JsonCommon *) $3;
+ p->output = (JsonOutput *) $4;
+ p->on_error = $5;
+ p->location = @1;
+ $$ = (Node *) p;
+ }
+ ;
+
+json_exists_error_clause_opt:
+ json_exists_error_behavior ON ERROR_P { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+json_exists_error_behavior:
+ json_behavior_error
+ | json_behavior_true
+ | json_behavior_false
+ | json_behavior_unknown
+ ;
+
+json_value_constructor:
+ json_object_constructor
+ | json_array_constructor
+ ;
+
+json_object_constructor:
+ JSON_OBJECT '(' json_object_args ')'
+ {
+ $$ = $3;
+ }
+ ;
+
+json_object_args:
+ json_object_constructor_args
+ | json_object_func_args
+ ;
+
+json_object_func_args:
+ func_arg_list
+ {
+ List *func = list_make1(makeString("json_object"));
+ $$ = (Node *) makeFuncCall(func, $1, COERCE_EXPLICIT_CALL, @1);
+ }
+ ;
+
+json_object_constructor_args:
+ json_object_constructor_args_opt json_output_clause_opt
+ {
+ JsonObjectConstructor *n = (JsonObjectConstructor *) $1;
+ n->output = (JsonOutput *) $2;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_object_constructor_args_opt:
+ json_name_and_value_list
+ json_object_constructor_null_clause_opt
+ json_key_uniqueness_constraint_opt
+ {
+ JsonObjectConstructor *n = makeNode(JsonObjectConstructor);
+ n->exprs = $1;
+ n->absent_on_null = $2;
+ n->unique = $3;
+ $$ = (Node *) n;
+ }
+ | /* EMPTY */
+ {
+ JsonObjectConstructor *n = makeNode(JsonObjectConstructor);
+ n->exprs = NULL;
+ n->absent_on_null = false;
+ n->unique = false;
+ $$ = (Node *) n;
+ }
+ ;
+
+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:
+/* TODO This is not supported due to conflicts
+ KEY c_expr VALUE_P json_value_expr %prec POSTFIXOP
+ { $$ = makeJsonKeyValue($2, $4); }
+ |
+*/
+ c_expr VALUE_P json_value_expr
+ { $$ = makeJsonKeyValue($1, $3); }
+ |
+ a_expr ':' json_value_expr
+ { $$ = makeJsonKeyValue($1, $3); }
+ ;
+
+json_object_constructor_null_clause_opt:
+ NULL_P ON NULL_P { $$ = false; }
+ | ABSENT ON NULL_P { $$ = true; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+json_array_constructor:
+ 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 = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+ /* n->format = $4; */
+ n->absent_on_null = true /* $5 */;
+ n->output = (JsonOutput *) $4;
+ 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;
+ }
+ ;
+
+json_value_expr_list:
+ json_value_expr { $$ = list_make1($1); }
+ | json_value_expr_list ',' json_value_expr { $$ = lappend($1, $3);}
+ ;
+
+json_array_constructor_null_clause_opt:
+ NULL_P ON NULL_P { $$ = false; }
+ | ABSENT ON NULL_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+json_aggregate_func:
+ json_object_aggregate_constructor
+ | json_array_aggregate_constructor
+ ;
+
+json_object_aggregate_constructor:
+ 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_array_aggregate_constructor:
+ 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; }
+ ;
/*****************************************************************************
*
@@ -15490,6 +16179,7 @@ BareColLabel: IDENT { $$ = $1; }
*/
unreserved_keyword:
ABORT_P
+ | ABSENT
| ABSOLUTE_P
| ACCESS
| ACTION
@@ -15530,6 +16220,7 @@ unreserved_keyword:
| COMMIT
| COMMITTED
| COMPRESSION
+ | CONDITIONAL
| CONFIGURATION
| CONFLICT
| CONNECTION
@@ -15566,10 +16257,12 @@ unreserved_keyword:
| DOUBLE_P
| DROP
| EACH
+ | EMPTY_P
| ENABLE_P
| ENCODING
| ENCRYPTED
| ENUM_P
+ | ERROR_P
| ESCAPE
| EVENT
| EXCLUDE
@@ -15586,6 +16279,7 @@ unreserved_keyword:
| FIRST_P
| FOLLOWING
| FORCE
+ | FORMAT
| FORWARD
| FUNCTION
| FUNCTIONS
@@ -15617,7 +16311,9 @@ unreserved_keyword:
| INSTEAD
| INVOKER
| ISOLATION
+ | KEEP
| KEY
+ | KEYS
| LABEL
| LANGUAGE
| LARGE_P
@@ -15660,6 +16356,7 @@ unreserved_keyword:
| OFF
| OIDS
| OLD
+ | OMIT
| OPERATOR
| OPTION
| OPTIONS
@@ -15689,6 +16386,7 @@ unreserved_keyword:
| PROGRAM
| PUBLICATION
| QUOTE
+ | QUOTES
| RANGE
| READ
| REASSIGN
@@ -15718,6 +16416,7 @@ unreserved_keyword:
| ROWS
| RULE
| SAVEPOINT
+ | SCALAR
| SCHEMA
| SCHEMAS
| SCROLL
@@ -15769,6 +16468,7 @@ unreserved_keyword:
| UESCAPE
| UNBOUNDED
| UNCOMMITTED
+ | UNCONDITIONAL
| UNENCRYPTED
| UNKNOWN
| UNLISTEN
@@ -15826,6 +16526,16 @@ col_name_keyword:
| INT_P
| INTEGER
| INTERVAL
+ | JSON
+ | JSON_ARRAY
+ | JSON_ARRAYAGG
+ | JSON_EXISTS
+ | JSON_OBJECT
+ | JSON_OBJECTAGG
+ | JSON_QUERY
+ | JSON_SCALAR
+ | JSON_SERIALIZE
+ | JSON_VALUE
| LEAST
| NATIONAL
| NCHAR
@@ -15893,6 +16603,7 @@ type_func_name_keyword:
| OVERLAPS
| RIGHT
| SIMILAR
+ | STRING
| TABLESAMPLE
| VERBOSE
;
@@ -15994,6 +16705,7 @@ reserved_keyword:
*/
bare_label_keyword:
ABORT_P
+ | ABSENT
| ABSOLUTE_P
| ACCESS
| ACTION
@@ -16056,6 +16768,7 @@ bare_label_keyword:
| COMMITTED
| COMPRESSION
| CONCURRENTLY
+ | CONDITIONAL
| CONFIGURATION
| CONFLICT
| CONNECTION
@@ -16108,11 +16821,13 @@ bare_label_keyword:
| DROP
| EACH
| ELSE
+ | EMPTY_P
| ENABLE_P
| ENCODING
| ENCRYPTED
| END_P
| ENUM_P
+ | ERROR_P
| ESCAPE
| EVENT
| EXCLUDE
@@ -16133,6 +16848,7 @@ bare_label_keyword:
| FOLLOWING
| FORCE
| FOREIGN
+ | FORMAT
| FORWARD
| FREEZE
| FULL
@@ -16177,7 +16893,19 @@ bare_label_keyword:
| IS
| ISOLATION
| JOIN
+ | JSON
+ | JSON_ARRAY
+ | JSON_ARRAYAGG
+ | JSON_EXISTS
+ | JSON_OBJECT
+ | JSON_OBJECTAGG
+ | JSON_QUERY
+ | JSON_SCALAR
+ | JSON_SERIALIZE
+ | JSON_VALUE
+ | KEEP
| KEY
+ | KEYS
| LABEL
| LANGUAGE
| LARGE_P
@@ -16234,6 +16962,7 @@ bare_label_keyword:
| OFF
| OIDS
| OLD
+ | OMIT
| ONLY
| OPERATOR
| OPTION
@@ -16270,6 +16999,7 @@ bare_label_keyword:
| PROGRAM
| PUBLICATION
| QUOTE
+ | QUOTES
| RANGE
| READ
| REAL
@@ -16303,6 +17033,7 @@ bare_label_keyword:
| ROWS
| RULE
| SAVEPOINT
+ | SCALAR
| SCHEMA
| SCHEMAS
| SCROLL
@@ -16337,6 +17068,7 @@ bare_label_keyword:
| STORAGE
| STORED
| STRICT_P
+ | STRING
| STRIP_P
| SUBSCRIPTION
| SUBSTRING
@@ -16370,6 +17102,7 @@ bare_label_keyword:
| UESCAPE
| UNBOUNDED
| UNCOMMITTED
+ | UNCONDITIONAL
| UNENCRYPTED
| UNIQUE
| UNKNOWN
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 4133526f04..9db8967c60 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -690,6 +690,10 @@ assign_collations_walker(Node *node, assign_collations_context *context)
&loccontext);
}
break;
+ case T_JsonExpr:
+ /* Context item and PASSING arguments are already
+ * marked with collations in parse_expr.c. */
+ break;
default:
/*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 2d1a477154..678840593b 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -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,8 @@
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/date.h"
+#include "utils/fmgroids.h"
+#include "utils/jsonb.h"
#include "utils/lsyscache.h"
#include "utils/timestamp.h"
#include "utils/xml.h"
@@ -74,6 +78,21 @@ 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 *transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *p);
+static Node *transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *p);
+static Node *transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve);
+static Node *transformJsonParseExpr(ParseState *pstate, JsonParseExpr *expr);
+static Node *transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *expr);
+static Node *transformJsonSerializeExpr(ParseState *pstate,
+ JsonSerializeExpr *expr);
static Node *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -301,6 +320,50 @@ 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;
+
+ case T_JsonIsPredicate:
+ result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr);
+ break;
+
+ case T_JsonFuncExpr:
+ result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr);
+ break;
+
+ case T_JsonValueExpr:
+ result = transformJsonValueExpr(pstate, (JsonValueExpr *) expr);
+ break;
+
+ case T_JsonParseExpr:
+ result = transformJsonParseExpr(pstate, (JsonParseExpr *) expr);
+ break;
+
+ case T_JsonScalarExpr:
+ result = transformJsonScalarExpr(pstate, (JsonScalarExpr *) expr);
+ break;
+
+ case T_JsonSerializeExpr:
+ result = transformJsonSerializeExpr(pstate, (JsonSerializeExpr *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3094,3 +3157,1411 @@ 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 == 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 *
+transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
+ JsonFormatType default_format, bool isarg,
+ Oid targettype)
+{
+ Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr);
+ Node *rawexpr;
+ JsonFormatType format;
+ Oid exprtype;
+ int location;
+ char typcategory;
+ bool typispreferred;
+
+ if (exprType(expr) == UNKNOWNOID)
+ expr = coerce_to_specific_type(pstate, expr, TEXTOID, "JSON_VALUE_EXPR");
+
+ rawexpr = expr;
+ exprtype = exprType(expr);
+ location = exprLocation(expr);
+
+ get_type_category_preferred(exprtype, &typcategory, &typispreferred);
+
+ rawexpr = expr;
+
+ if (ve->format->format != 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;
+ }
+ else if (isarg)
+ {
+ /* Pass SQL/JSON item types directly without conversion to json[b]. */
+ switch (exprtype)
+ {
+ case TEXTOID:
+ case NUMERICOID:
+ case BOOLOID:
+ case INT2OID:
+ case INT4OID:
+ case INT8OID:
+ case FLOAT4OID:
+ case FLOAT8OID:
+ case DATEOID:
+ case TIMEOID:
+ case TIMETZOID:
+ case TIMESTAMPOID:
+ case TIMESTAMPTZOID:
+ return expr;
+
+ default:
+ if (typcategory == TYPCATEGORY_STRING)
+ return coerce_to_specific_type(pstate, expr, TEXTOID,
+ "JSON_VALUE_EXPR");
+ /* else convert argument to json[b] type */
+ break;
+ }
+
+ format = default_format;
+ }
+ 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 &&
+ (!OidIsValid(targettype) || exprtype == targettype))
+ expr = rawexpr;
+ else
+ {
+ Node *orig = makeCaseTestExpr(expr);
+ Node *coerced;
+ bool cast_is_needed = OidIsValid(targettype);
+
+ if (!isarg && !cast_is_needed &&
+ exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg(ve->format->format == 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)));
+
+ expr = orig;
+
+ /* Convert encoded JSON text from bytea. */
+ if (format == JS_FORMAT_JSON && exprtype == BYTEAOID)
+ {
+ expr = makeJsonByteaToTextConversion(expr, ve->format, location);
+ exprtype = TEXTOID;
+ }
+
+ if (!OidIsValid(targettype))
+ targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
+
+ /* Try to coerce to the target type. */
+ coerced = coerce_to_target_type(pstate, expr, exprtype,
+ targettype, -1,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+
+ if (!coerced)
+ {
+ /* If coercion failed, use to_json()/to_jsonb() functions. */
+ FuncExpr *fexpr;
+ Oid fnoid;
+
+ if (cast_is_needed) /* only CAST is allowed */
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprtype),
+ format_type_be(targettype)),
+ parser_errposition(pstate, location)));
+
+ fnoid = targettype == JSONOID ? F_TO_JSON : F_TO_JSONB;
+ fexpr = makeFuncExpr(fnoid, targettype, list_make1(expr),
+ InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
+
+ fexpr->location = location;
+
+ 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;
+}
+
+/*
+ * Transform JSON value expression using FORMAT JSON by default.
+ */
+static Node *
+transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve)
+{
+ return transformJsonValueExprExt(pstate, jve, JS_FORMAT_JSON, false,
+ InvalidOid);
+}
+
+/*
+ * Transform JSON value expression using unspecified format by default.
+ */
+static Node *
+transformJsonValueExprDefault(ParseState *pstate, JsonValueExpr *jve)
+{
+ return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false,
+ InvalidOid);
+}
+
+/*
+ * 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 != 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 == 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 == JS_FORMAT_DEFAULT)
+ /* assign JSONB format when returning jsonb, or JSON format otherwise */
+ ret->format->format =
+ 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 contructor 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_json = false;
+ bool have_jsonb = false;
+
+ foreach(lc, args)
+ {
+ Node *expr = lfirst(lc);
+ Oid typid = exprType(expr);
+
+ have_json |= typid == JSONOID;
+ have_jsonb |= typid == JSONBOID;
+
+ if (have_jsonb)
+ break;
+ }
+
+ if (have_jsonb)
+ {
+ returning->typid = JSONBOID;
+ returning->format->format = JS_FORMAT_JSONB;
+ }
+ else if (have_json)
+ {
+ returning->typid = JSONOID;
+ returning->format->format = JS_FORMAT_JSON;
+ }
+ else
+ {
+ /* XXX TEXT is default by the standard, but we return JSON */
+ returning->typid = JSONOID;
+ returning->format->format = 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 ? returning->format->location : -1;
+
+ /* special case for RETURNING bytea FORMAT json */
+ if (returning->format->format == 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 == 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 = transformJsonValueExprDefault(pstate, kv->value);
+
+ 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 = transformJsonValueExprDefault(pstate, agg->arg->value);
+ args = list_make2(key, val);
+
+ returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
+ args);
+
+ if (returning->format->format == JS_FORMAT_JSONB)
+ {
+ if (agg->absent_on_null)
+ if (agg->unique)
+ aggfnname = "pg_catalog.jsonb_object_agg_unique_strict"; /* F_JSONB_OBJECT_AGG_UNIQUE_STRICT */
+ else
+ aggfnname = "pg_catalog.jsonb_object_agg_strict"; /* F_JSONB_OBJECT_AGG_STRICT */
+ else
+ if (agg->unique)
+ aggfnname = "pg_catalog.jsonb_object_agg_unique"; /* F_JSONB_OBJECT_AGG_UNIQUE */
+ else
+ aggfnname = "pg_catalog.jsonb_object_agg"; /* F_JSONB_OBJECT_AGG */
+
+ aggtype = JSONBOID;
+ }
+ else
+ {
+ if (agg->absent_on_null)
+ if (agg->unique)
+ aggfnname = "pg_catalog.json_object_agg_unique_strict"; /* F_JSON_OBJECT_AGG_UNIQUE_STRICT */
+ else
+ aggfnname = "pg_catalog.json_object_agg_strict"; /* F_JSON_OBJECT_AGG_STRICT */
+ else
+ if (agg->unique)
+ aggfnname = "pg_catalog.json_object_agg_unique"; /* F_JSON_OBJECT_AGG_UNIQUE */
+ else
+ aggfnname = "pg_catalog.json_object_agg"; /* F_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 = transformJsonValueExprDefault(pstate, agg->arg);
+
+ returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
+ list_make1(arg));
+
+ if (returning->format->format == 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 = transformJsonValueExprDefault(pstate, jsval);
+
+ 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);
+}
+
+static Node *
+transformJsonParseArg(ParseState *pstate, Node *jsexpr, JsonFormat *format,
+ Oid *exprtype)
+{
+ Node *raw_expr = transformExprRecurse(pstate, jsexpr);
+ Node *expr = raw_expr;
+
+ *exprtype = exprType(expr);
+
+ /* prepare input document */
+ if (*exprtype == BYTEAOID)
+ {
+ JsonValueExpr *jve;
+
+ expr = makeCaseTestExpr(raw_expr);
+ expr = makeJsonByteaToTextConversion(expr, format, exprLocation(expr));
+ *exprtype = TEXTOID;
+
+ jve = makeJsonValueExpr((Expr *) raw_expr, format);
+
+ jve->formatted_expr = (Expr *) expr;
+ expr = (Node *) jve;
+ }
+ else
+ {
+ char typcategory;
+ bool typispreferred;
+
+ get_type_category_preferred(*exprtype, &typcategory, &typispreferred);
+
+ if (*exprtype == UNKNOWNOID || typcategory == TYPCATEGORY_STRING)
+ {
+ expr = coerce_to_target_type(pstate, (Node *) expr, *exprtype,
+ TEXTOID, -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST, -1);
+ *exprtype = TEXTOID;
+ }
+
+ if (format->encoding != JS_ENC_DEFAULT)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ parser_errposition(pstate, format->location),
+ errmsg("cannot use JSON FORMAT ENCODING clause for non-bytea input types")));
+ }
+
+ return expr;
+}
+
+/*
+ * Transform IS JSON predicate into
+ * json[b]_is_valid(json, value_type [, check_key_uniqueness]) call.
+ */
+static Node *
+transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred)
+{
+ Oid exprtype;
+ Node *expr = transformJsonParseArg(pstate, pred->expr, pred->format,
+ &exprtype);
+
+ /* make resulting expression */
+ if (exprtype != TEXTOID && exprtype != JSONOID && exprtype != JSONBOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot use type %s in IS JSON predicate",
+ format_type_be(exprtype))));
+
+ return makeJsonIsPredicate(expr, NULL, pred->value_type,
+ pred->unique_keys, pred->location);
+}
+
+/*
+ * Transform a JSON PASSING clause.
+ */
+static void
+transformJsonPassingArgs(ParseState *pstate, JsonFormatType format, List *args,
+ List **passing_values, List **passing_names)
+{
+ ListCell *lc;
+
+ *passing_values = NIL;
+ *passing_names = NIL;
+
+ foreach(lc, args)
+ {
+ JsonArgument *arg = castNode(JsonArgument, lfirst(lc));
+ Node *expr = transformJsonValueExprExt(pstate, arg->val,
+ format, true, InvalidOid);
+
+ assign_expr_collations(pstate, expr);
+
+ *passing_values = lappend(*passing_values, expr);
+ *passing_names = lappend(*passing_names, makeString(arg->name));
+ }
+}
+
+/*
+ * Transform a JSON BEHAVIOR clause.
+ */
+static JsonBehavior *
+transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior,
+ JsonBehaviorType default_behavior)
+{
+ JsonBehaviorType behavior_type;
+ Node *default_expr;
+
+ behavior_type = behavior ? behavior->btype : default_behavior;
+ default_expr = behavior_type != JSON_BEHAVIOR_DEFAULT ? NULL :
+ transformExprRecurse(pstate, behavior->default_expr);
+
+ return makeJsonBehavior(behavior_type, default_expr);
+}
+
+/*
+ * Common code for JSON_VALUE, JSON_QUERY, JSON_EXISTS transformation
+ * into a JsonExpr node.
+ */
+static JsonExpr *
+transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func)
+{
+ JsonExpr *jsexpr = makeNode(JsonExpr);
+ Node *pathspec;
+ JsonFormatType format;
+
+ if (func->common->pathname)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("JSON_TABLE path name is not allowed here"),
+ parser_errposition(pstate, func->location)));
+
+ jsexpr->location = func->location;
+ jsexpr->op = func->op;
+ jsexpr->formatted_expr = transformJsonValueExpr(pstate, func->common->expr);
+
+ assign_expr_collations(pstate, jsexpr->formatted_expr);
+
+ /* format is determined by context item type */
+ format = exprType(jsexpr->formatted_expr) == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON;
+
+ jsexpr->result_coercion = NULL;
+ jsexpr->omit_quotes = false;
+
+ jsexpr->format = func->common->expr->format;
+
+ pathspec = transformExprRecurse(pstate, func->common->pathspec);
+
+ jsexpr->path_spec =
+ coerce_to_target_type(pstate, pathspec, exprType(pathspec),
+ JSONPATHOID, -1,
+ COERCION_EXPLICIT, COERCE_IMPLICIT_CAST,
+ exprLocation(pathspec));
+ if (!jsexpr->path_spec)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("JSON path expression must be type %s, not type %s",
+ "jsonpath", format_type_be(exprType(pathspec))),
+ parser_errposition(pstate, exprLocation(pathspec))));
+
+ /* transform and coerce to json[b] passing arguments */
+ transformJsonPassingArgs(pstate, format, func->common->passing,
+ &jsexpr->passing_values, &jsexpr->passing_names);
+
+ if (func->op != IS_JSON_EXISTS)
+ jsexpr->on_empty = transformJsonBehavior(pstate, func->on_empty,
+ JSON_BEHAVIOR_NULL);
+
+ jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
+ func->op == IS_JSON_EXISTS ?
+ JSON_BEHAVIOR_FALSE :
+ JSON_BEHAVIOR_NULL);
+
+ return jsexpr;
+}
+
+/*
+ * Assign default JSON returning type from the specified format or from
+ * the context item type.
+ */
+static void
+assignDefaultJsonReturningType(Node *context_item, JsonFormat *context_format,
+ JsonReturning *ret)
+{
+ bool is_jsonb;
+
+ ret->format = copyObject(context_format);
+
+ if (ret->format->format == JS_FORMAT_DEFAULT)
+ is_jsonb = exprType(context_item) == JSONBOID;
+ else
+ is_jsonb = ret->format->format == JS_FORMAT_JSONB;
+
+ ret->typid = is_jsonb ? JSONBOID : JSONOID;
+ ret->typmod = -1;
+}
+
+/*
+ * Try to coerce expression to the output type or
+ * use json_populate_type() for composite, array and domain types or
+ * use coercion via I/O.
+ */
+static JsonCoercion *
+coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning)
+{
+ char typtype;
+ JsonCoercion *coercion = makeNode(JsonCoercion);
+
+ coercion->expr = coerceJsonFuncExpr(pstate, expr, returning, false);
+
+ if (coercion->expr)
+ {
+ if (coercion->expr == expr)
+ coercion->expr = NULL;
+
+ return coercion;
+ }
+
+ typtype = get_typtype(returning->typid);
+
+ if (returning->typid == RECORDOID ||
+ typtype == TYPTYPE_COMPOSITE ||
+ typtype == TYPTYPE_DOMAIN ||
+ type_is_array(returning->typid))
+ coercion->via_populate = true;
+ else
+ coercion->via_io = true;
+
+ return coercion;
+}
+
+/*
+ * Transform a JSON output clause of JSON_VALUE and JSON_QUERY.
+ */
+static void
+transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
+ JsonExpr *jsexpr)
+{
+ Node *expr = jsexpr->formatted_expr;
+
+ jsexpr->returning = transformJsonOutput(pstate, func->output, false);
+
+ /* JSON_VALUE returns text by default */
+ if (func->op == IS_JSON_VALUE && !OidIsValid(jsexpr->returning->typid))
+ {
+ jsexpr->returning->typid = TEXTOID;
+ jsexpr->returning->typmod = -1;
+ }
+
+ if (OidIsValid(jsexpr->returning->typid))
+ {
+ JsonReturning ret;
+
+ if (func->op == IS_JSON_VALUE &&
+ jsexpr->returning->typid != JSONOID &&
+ jsexpr->returning->typid != JSONBOID)
+ {
+ /* Forced coercion via I/O for JSON_VALUE for non-JSON types */
+ jsexpr->result_coercion = makeNode(JsonCoercion);
+ jsexpr->result_coercion->expr = NULL;
+ jsexpr->result_coercion->via_io = true;
+ return;
+ }
+
+ assignDefaultJsonReturningType(jsexpr->formatted_expr, jsexpr->format, &ret);
+
+ if (ret.typid != jsexpr->returning->typid ||
+ ret.typmod != jsexpr->returning->typmod)
+ {
+ Node *placeholder = makeCaseTestExpr(expr);
+
+ Assert(((CaseTestExpr *) placeholder)->typeId == ret.typid);
+ Assert(((CaseTestExpr *) placeholder)->typeMod == ret.typmod);
+
+ jsexpr->result_coercion = coerceJsonExpr(pstate, placeholder,
+ jsexpr->returning);
+ }
+ }
+ else
+ assignDefaultJsonReturningType(jsexpr->formatted_expr, jsexpr->format,
+ jsexpr->returning);
+}
+
+/*
+ * Coerce a expression in JSON DEFAULT behavior to the target output type.
+ */
+static Node *
+coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr, Node *defexpr)
+{
+ int location;
+ Oid exprtype;
+
+ if (!defexpr)
+ return NULL;
+
+ exprtype = exprType(defexpr);
+ location = exprLocation(defexpr);
+
+ if (location < 0)
+ location = jsexpr->location;
+
+ defexpr = coerce_to_target_type(pstate,
+ defexpr,
+ exprtype,
+ jsexpr->returning->typid,
+ jsexpr->returning->typmod,
+ COERCION_EXPLICIT,
+ COERCE_IMPLICIT_CAST,
+ location);
+
+ if (!defexpr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast DEFAULT expression type %s to %s",
+ format_type_be(exprtype),
+ format_type_be(jsexpr->returning->typid)),
+ parser_errposition(pstate, location)));
+
+ return defexpr;
+}
+
+/*
+ * Initialize SQL/JSON item coercion from the SQL type "typid" to the target
+ * "returning" type.
+ */
+static JsonCoercion *
+initJsonItemCoercion(ParseState *pstate, Oid typid,
+ const JsonReturning *returning)
+{
+ Node *expr;
+
+ if (typid == UNKNOWNOID)
+ {
+ expr = (Node *) makeNullConst(UNKNOWNOID, -1, InvalidOid);
+ }
+ else
+ {
+ CaseTestExpr *placeholder = makeNode(CaseTestExpr);
+
+ placeholder->typeId = typid;
+ placeholder->typeMod = -1;
+ placeholder->collation = InvalidOid;
+
+ expr = (Node *) placeholder;
+ }
+
+ return coerceJsonExpr(pstate, expr, returning);
+}
+
+static void
+initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions,
+ const JsonReturning *returning, Oid contextItemTypeId)
+{
+ struct
+ {
+ JsonCoercion **coercion;
+ Oid typid;
+ } *p,
+ coercionTypids[] =
+ {
+ { &coercions->null, UNKNOWNOID },
+ { &coercions->string, TEXTOID },
+ { &coercions->numeric, NUMERICOID },
+ { &coercions->boolean, BOOLOID },
+ { &coercions->date, DATEOID },
+ { &coercions->time, TIMEOID },
+ { &coercions->timetz, TIMETZOID },
+ { &coercions->timestamp, TIMESTAMPOID },
+ { &coercions->timestamptz, TIMESTAMPTZOID },
+ { &coercions->composite, contextItemTypeId },
+ { NULL, InvalidOid }
+ };
+
+ for (p = coercionTypids; p->coercion; p++)
+ *p->coercion = initJsonItemCoercion(pstate, p->typid, returning);
+}
+
+/*
+ * Transform JSON_VALUE, JSON_QUERY, JSON_EXISTS functions into a JsonExpr node.
+ */
+static Node *
+transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
+{
+ JsonExpr *jsexpr = transformJsonExprCommon(pstate, func);
+ const char *func_name = NULL;
+ Node *contextItemExpr = jsexpr->formatted_expr;
+
+ switch (func->op)
+ {
+ case IS_JSON_VALUE:
+ func_name = "JSON_VALUE";
+
+ transformJsonFuncExprOutput(pstate, func, jsexpr);
+
+ jsexpr->returning->format->format = JS_FORMAT_DEFAULT;
+ jsexpr->returning->format->encoding = JS_ENC_DEFAULT;
+
+ jsexpr->on_empty->default_expr =
+ coerceDefaultJsonExpr(pstate, jsexpr,
+ jsexpr->on_empty->default_expr);
+
+ jsexpr->on_error->default_expr =
+ coerceDefaultJsonExpr(pstate, jsexpr,
+ jsexpr->on_error->default_expr);
+
+ jsexpr->coercions = makeNode(JsonItemCoercions);
+ initJsonItemCoercions(pstate, jsexpr->coercions, jsexpr->returning,
+ exprType(contextItemExpr));
+
+ break;
+
+ case IS_JSON_QUERY:
+ func_name = "JSON_QUERY";
+
+ transformJsonFuncExprOutput(pstate, func, jsexpr);
+
+ jsexpr->on_empty->default_expr =
+ coerceDefaultJsonExpr(pstate, jsexpr,
+ jsexpr->on_empty->default_expr);
+
+ jsexpr->on_error->default_expr =
+ coerceDefaultJsonExpr(pstate, jsexpr,
+ jsexpr->on_error->default_expr);
+
+ jsexpr->wrapper = func->wrapper;
+ jsexpr->omit_quotes = func->omit_quotes;
+
+ break;
+
+ case IS_JSON_EXISTS:
+ func_name = "JSON_EXISTS";
+
+ jsexpr->returning = transformJsonOutput(pstate, func->output, false);
+
+ jsexpr->returning->format->format = JS_FORMAT_DEFAULT;
+ jsexpr->returning->format->encoding = JS_ENC_DEFAULT;
+
+ if (!OidIsValid(jsexpr->returning->typid))
+ {
+ jsexpr->returning->typid = BOOLOID;
+ jsexpr->returning->typmod = -1;
+ }
+ else if (jsexpr->returning->typid != BOOLOID)
+ {
+ CaseTestExpr *placeholder = makeNode(CaseTestExpr);
+ int location = exprLocation((Node *) jsexpr);
+
+ placeholder->typeId = BOOLOID;
+ placeholder->typeMod = -1;
+ placeholder->collation = InvalidOid;
+
+ jsexpr->result_coercion = makeNode(JsonCoercion);
+ jsexpr->result_coercion->expr =
+ coerce_to_target_type(pstate, (Node *) placeholder, BOOLOID,
+ jsexpr->returning->typid,
+ jsexpr->returning->typmod,
+ COERCION_EXPLICIT,
+ COERCE_IMPLICIT_CAST,
+ location);
+
+ if (!jsexpr->result_coercion->expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(BOOLOID),
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, location, (Node *) jsexpr)));
+
+ if (jsexpr->result_coercion->expr == (Node *) placeholder)
+ jsexpr->result_coercion->expr = NULL;
+ }
+ break;
+ }
+
+ if (exprType(contextItemExpr) != JSONBOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s() is not yet implemented for json type", func_name),
+ parser_errposition(pstate, func->location)));
+
+ return (Node *) jsexpr;
+}
+
+static JsonReturning *
+transformJsonConstructorRet(ParseState *pstate, JsonOutput *output, const char *fname)
+{
+ JsonReturning *returning;
+
+ if (output)
+ {
+ returning = transformJsonOutput(pstate, output, false);
+
+ Assert(OidIsValid(returning->typid));
+
+ if (returning->typid != JSONOID && returning->typid != JSONBOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot use RETURNING type %s in %s",
+ format_type_be(returning->typid), fname),
+ parser_errposition(pstate, output->typeName->location)));
+ }
+ else
+ {
+ Oid targettype = SQLJSON_TYPE_OID();
+ JsonFormatType format =
+ SQLJSON_TYPE_IS_JSONB() ? JS_FORMAT_JSONB : JS_FORMAT_JSON;
+
+ returning = makeNode(JsonReturning);
+ returning->format = makeJsonFormat(format, JS_ENC_DEFAULT, -1);
+ returning->typid = targettype;
+ returning->typmod = -1;
+ }
+
+ return returning;
+}
+
+/*
+ * Transform a JSON() expression.
+ */
+static Node *
+transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr)
+{
+ JsonReturning *returning = transformJsonConstructorRet(pstate, jsexpr->output,
+ "JSON()");
+ Node *arg;
+
+ if (jsexpr->unique_keys)
+ {
+ /*
+ * Coerce string argument to text and then to json[b] in the executor
+ * node with key uniqueness check.
+ */
+ JsonValueExpr *jve = jsexpr->expr;
+ Oid arg_type;
+
+ arg = transformJsonParseArg(pstate, (Node *) jve->raw_expr, jve->format,
+ &arg_type);
+
+ if (arg_type != TEXTOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot use non-string types with WITH UNIQUE KEYS clause"),
+ parser_errposition(pstate, jsexpr->location)));
+ }
+ else
+ {
+ /*
+ * Coerce argument to target type using CAST for compatibilty with PG
+ * function-like CASTs.
+ */
+ arg = transformJsonValueExprExt(pstate, jsexpr->expr, JS_FORMAT_JSON,
+ false, returning->typid);
+ }
+
+ return makeJsonConstructorExpr(pstate, JSCTOR_JSON_PARSE, list_make1(arg), NULL,
+ returning, jsexpr->unique_keys, false,
+ jsexpr->location);
+}
+
+/*
+ * Transform a JSON_SCALAR() expression.
+ */
+static Node *
+transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *jsexpr)
+{
+ Node *arg = transformExprRecurse(pstate, (Node *) jsexpr->expr);
+ JsonReturning *returning = transformJsonConstructorRet(pstate, jsexpr->output,
+ "JSON_SCALAR()");
+
+ if (exprType(arg) == UNKNOWNOID)
+ arg = coerce_to_specific_type(pstate, arg, TEXTOID, "JSON_SCALAR");
+
+ return makeJsonConstructorExpr(pstate, JSCTOR_JSON_SCALAR, list_make1(arg), NULL,
+ returning, false, false, jsexpr->location);
+}
+
+/*
+ * Transform a JSON_SERIALIZE() expression.
+ */
+static Node *
+transformJsonSerializeExpr(ParseState *pstate, JsonSerializeExpr *expr)
+{
+ Node *arg = transformJsonValueExpr(pstate, expr->expr);
+ JsonReturning *returning;
+
+ if (expr->output)
+ returning = transformJsonOutput(pstate, expr->output, true);
+ else
+ {
+ /* RETURNING TEXT FORMAT JSON is by default */
+ returning = makeNode(JsonReturning);
+ returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1);
+ returning->typid = TEXTOID;
+ returning->typmod = -1;
+ }
+
+ return makeJsonConstructorExpr(pstate, JSCTOR_JSON_SERIALIZE, list_make1(arg),
+ NULL, returning, false, false, expr->location);
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 6e8fbc4780..f390b29637 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1958,6 +1958,43 @@ FigureColnameInternal(Node *node, char **name)
case T_XmlSerialize:
*name = "xmlserialize";
return 2;
+ case T_JsonParseExpr:
+ *name = "json";
+ return 2;
+ case T_JsonScalarExpr:
+ *name = "json_scalar";
+ return 2;
+ case T_JsonSerializeExpr:
+ *name = "json_serialize";
+ return 2;
+ case T_JsonObjectConstructor:
+ *name = "json_object";
+ return 2;
+ case T_JsonArrayConstructor:
+ case T_JsonArrayQueryConstructor:
+ *name = "json_array";
+ return 2;
+ case T_JsonObjectAgg:
+ *name = "json_objectagg";
+ return 2;
+ case T_JsonArrayAgg:
+ *name = "json_arrayagg";
+ return 2;
+ case T_JsonFuncExpr:
+ /* make SQL/JSON functions act like a regular function */
+ switch (((JsonFuncExpr *) node)->op)
+ {
+ case IS_JSON_QUERY:
+ *name = "json_query";
+ return 2;
+ case IS_JSON_VALUE:
+ *name = "json_value";
+ return 2;
+ case IS_JSON_EXISTS:
+ *name = "json_exists";
+ return 2;
+ }
+ break;
default:
break;
}
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 875de7ba28..683e1ebcfa 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -150,6 +150,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;
}
@@ -221,6 +224,19 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
case ORDINALITY:
cur_token = WITH_LA;
break;
+ case UNIQUE:
+ cur_token = WITH_LA_UNIQUE;
+ break;
+ }
+ break;
+
+ case WITHOUT:
+ /* Replace WITHOUT by WITHOUT_LA if it's followed by TIME */
+ switch (next_token)
+ {
+ case TIME:
+ cur_token = WITHOUT_LA;
+ break;
}
break;
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 0e8e065457..0f7646996a 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -23,6 +23,7 @@
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
+#include "utils/jsonb.h"
#include "utils/lsyscache.h"
#include "utils/numeric.h"
#include "utils/syscache.h"
@@ -294,6 +295,14 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
else
buf = pstrdup("character varying");
break;
+
+ case JSONOID:
+ buf = pstrdup(SQLJSON_TYPE_IS_JSONB() ? "pg_catalog.json" : "json");
+ break;
+
+ case JSONBOID:
+ buf = pstrdup(SQLJSON_TYPE_IS_JSONB() ? "json" : "jsonb");
+ break;
}
if (buf == NULL)
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index a1145e2721..1f88f8d828 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -1018,11 +1018,6 @@ typedef struct NUMProc
*L_currency_symbol;
} NUMProc;
-/* Return flags for DCH_from_char() */
-#define DCH_DATED 0x01
-#define DCH_TIMED 0x02
-#define DCH_ZONED 0x04
-
/* ----------
* Functions
* ----------
@@ -6673,3 +6668,43 @@ float8_to_char(PG_FUNCTION_ARGS)
NUM_TOCHAR_finish;
PG_RETURN_TEXT_P(result);
}
+
+int
+datetime_format_flags(const char *fmt_str, bool *have_error)
+{
+ bool incache;
+ int fmt_len = strlen(fmt_str);
+ int result;
+ FormatNode *format;
+
+ if (fmt_len > DCH_CACHE_SIZE)
+ {
+ /*
+ * Allocate new memory if format picture is bigger than static cache
+ * and do not use cache (call parser always)
+ */
+ incache = false;
+
+ format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
+
+ parse_format(format, fmt_str, DCH_keywords,
+ DCH_suff, DCH_index, DCH_FLAG, NULL);
+ }
+ else
+ {
+ /*
+ * Use cache buffers
+ */
+ DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, false);
+
+ incache = true;
+ format = ent->format;
+ }
+
+ result = DCH_datetime_type(format, have_error);
+
+ if (!incache)
+ pfree(format);
+
+ return result;
+}
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 30ca2cf6c8..3e5bb37369 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -13,7 +13,10 @@
*/
#include "postgres.h"
+#include "access/hash.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"
@@ -27,20 +30,41 @@
#include "utils/lsyscache.h"
#include "utils/typcache.h"
-typedef enum /* type categories for datum_to_json */
+/* 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 for key uniqueness check in builder functions */
+typedef struct JsonUniqueBuilderState
+{
+ JsonUniqueCheckState check; /* unique check */
+ StringInfoData skipped_keys; /* skipped keys with NULL values */
+ MemoryContext mcxt; /* context for saving skipped keys */
+} JsonUniqueBuilderState;
+
+/* Element of object stack for key uniqueness check during json parsing */
+typedef struct JsonUniqueStackEntry
{
- JSONTYPE_NULL, /* null, so we didn't bother to identify */
- JSONTYPE_BOOL, /* boolean (built-in types only) */
- JSONTYPE_NUMERIC, /* numeric (ditto) */
- JSONTYPE_DATE, /* we use special formatting for datetimes */
- JSONTYPE_TIMESTAMP,
- JSONTYPE_TIMESTAMPTZ,
- JSONTYPE_JSON, /* JSON itself (and JSONB) */
- JSONTYPE_ARRAY, /* array */
- JSONTYPE_COMPOSITE, /* composite */
- JSONTYPE_CAST, /* something with an explicit cast to JSON */
- JSONTYPE_OTHER /* all else */
-} JsonTypeCategory;
+ struct JsonUniqueStackEntry *parent;
+ int object_id;
+} JsonUniqueStackEntry;
+
+/* State for key uniqueness check during json parsing */
+typedef struct JsonUniqueParsingState
+{
+ JsonLexContext *lex;
+ JsonUniqueCheckState check;
+ JsonUniqueStackEntry *stack;
+ int id_counter;
+ bool unique;
+} JsonUniqueParsingState;
typedef struct JsonAggState
{
@@ -49,6 +73,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,
@@ -59,9 +84,6 @@ static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
bool use_line_feeds);
static void array_to_json_internal(Datum array, StringInfo result,
bool use_line_feeds);
-static void json_categorize_type(Oid typoid,
- JsonTypeCategory *tcategory,
- Oid *outfuncoid);
static void datum_to_json(Datum val, bool is_null, StringInfo result,
JsonTypeCategory tcategory, Oid outfuncoid,
bool key_scalar);
@@ -140,7 +162,7 @@ json_recv(PG_FUNCTION_ARGS)
* output function OID. If the returned category is JSONTYPE_CAST, we
* return the OID of the type->JSON cast function instead.
*/
-static void
+void
json_categorize_type(Oid typoid,
JsonTypeCategory *tcategory,
Oid *outfuncoid)
@@ -722,6 +744,48 @@ row_to_json_pretty(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
}
+Datum
+to_json_worker(Datum val, JsonTypeCategory tcategory, Oid outfuncoid)
+{
+ StringInfo result = makeStringInfo();
+
+ datum_to_json(val, false, result, tcategory, outfuncoid, false);
+
+ return PointerGetDatum(cstring_to_text_with_len(result->data, result->len));
+}
+
+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:
+ 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:
+ default:
+ return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE;
+ }
+}
+
/*
* SQL function to_json(anyvalue)
*/
@@ -730,7 +794,6 @@ to_json(PG_FUNCTION_ARGS)
{
Datum val = PG_GETARG_DATUM(0);
Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
- StringInfo result;
JsonTypeCategory tcategory;
Oid outfuncoid;
@@ -742,11 +805,7 @@ to_json(PG_FUNCTION_ARGS)
json_categorize_type(val_type,
&tcategory, &outfuncoid);
- result = makeStringInfo();
-
- datum_to_json(val, false, result, tcategory, outfuncoid, false);
-
- PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
+ PG_RETURN_DATUM(to_json_worker(val, tcategory, outfuncoid));
}
/*
@@ -754,8 +813,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;
@@ -795,9 +854,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))
{
@@ -809,7 +873,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))
{
@@ -827,6 +891,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
*/
@@ -850,18 +933,122 @@ 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);
+}
+
+/* Functions implementing object key uniqueness check */
+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_check_free(JsonUniqueCheckState *cxt)
+{
+ hash_destroy(*cxt);
+}
+
+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;
+}
+
+static void
+json_unique_builder_init(JsonUniqueBuilderState *cxt)
+{
+ json_unique_check_init(&cxt->check);
+ cxt->mcxt = CurrentMemoryContext;
+ cxt->skipped_keys.data = NULL;
+}
+
+static void
+json_unique_builder_free(JsonUniqueBuilderState *cxt)
+{
+ json_unique_check_free(&cxt->check);
+
+ if (cxt->skipped_keys.data)
+ pfree(cxt->skipped_keys.data);
+}
+
+/* On-demand initialization of skipped_keys StringInfo structure */
+static StringInfo
+json_unique_builder_get_skipped_keys(JsonUniqueBuilderState *cxt)
+{
+ StringInfo out = &cxt->skipped_keys;
+
+ if (!out->data)
+ {
+ MemoryContext oldcxt = MemoryContextSwitchTo(cxt->mcxt);
+ initStringInfo(out);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ 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))
{
@@ -882,6 +1069,10 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
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);
@@ -909,7 +1100,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
else
{
state = (JsonAggState *) PG_GETARG_POINTER(0);
- appendStringInfoString(state->str, ", ");
}
/*
@@ -925,11 +1115,49 @@ json_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 */
+ skip = absent_on_null && PG_ARGISNULL(2);
+
+ if (skip)
+ {
+ /* If key uniqueness check is needed we must save skipped keys */
+ if (!unique_keys)
+ PG_RETURN_POINTER(state);
+
+ out = json_unique_builder_get_skipped_keys(&state->unique_check);
+ }
+ else
+ {
+ out = state->str;
+
+ /*
+ * Append comma delimiter only if we have already outputted 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))
@@ -943,6 +1171,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.
*/
@@ -960,6 +1224,8 @@ json_object_agg_finalfn(PG_FUNCTION_ARGS)
if (state == NULL)
PG_RETURN_NULL();
+ json_unique_builder_free(&state->unique_check);
+
/* Else return state with appropriate object terminator added */
PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, " }"));
}
@@ -984,25 +1250,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,
@@ -1016,19 +1271,58 @@ 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_skipped_keys(&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),
+ errmsg("argument %d cannot be null", i + 1),
errhint("Object keys should be text.")));
- add_json(args[i], false, result, types[i], true);
+ /* save key offset before key appending */
+ key_offset = out->len;
+
+ 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, " : ");
@@ -1038,7 +1332,29 @@ json_build_object(PG_FUNCTION_ARGS)
appendStringInfoChar(result, '}');
- PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
+ if (unique_keys)
+ json_unique_builder_free(&unique_check);
+
+ 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));
}
/*
@@ -1050,25 +1366,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();
@@ -1076,6 +1380,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);
@@ -1083,7 +1390,26 @@ 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));
}
/*
@@ -1317,6 +1643,106 @@ escape_json(StringInfo buf, const char *str)
appendStringInfoCharMacro(buf, '"');
}
+/* Semantic actions for key uniqueness check */
+static void
+json_unique_object_start(void *_state)
+{
+ JsonUniqueParsingState *state = _state;
+ JsonUniqueStackEntry *entry;
+
+ if (!state->unique)
+ return;
+
+ /* push object entry to stack */
+ entry = palloc(sizeof(*entry));
+ entry->object_id = state->id_counter++;
+ entry->parent = state->stack;
+ state->stack = entry;
+}
+
+static void
+json_unique_object_end(void *_state)
+{
+ JsonUniqueParsingState *state = _state;
+ JsonUniqueStackEntry *entry;
+
+ if (!state->unique)
+ return;
+
+ entry = state->stack;
+ state->stack = entry->parent; /* pop object from stack */
+ pfree(entry);
+}
+
+static void
+json_unique_object_field_start(void *_state, char *field, bool isnull)
+{
+ JsonUniqueParsingState *state = _state;
+ JsonUniqueStackEntry *entry;
+
+ if (!state->unique)
+ return;
+
+ /* find key collision in the current object */
+ if (json_unique_check_key(&state->check, field, state->stack->object_id))
+ return;
+
+ state->unique = false;
+
+ /* pop all objects entries */
+ while ((entry = state->stack))
+ {
+ state->stack = entry->parent;
+ pfree(entry);
+ }
+}
+
+/* Validate JSON text and additionally check key uniqueness */
+bool
+json_validate(text *json, bool check_unique_keys, bool throw_error)
+{
+ JsonLexContext *lex = makeJsonLexContext(json, check_unique_keys);
+ JsonSemAction uniqueSemAction = {0};
+ JsonUniqueParsingState state;
+ JsonParseErrorType result;
+
+ if (check_unique_keys)
+ {
+ state.lex = lex;
+ state.stack = NULL;
+ state.id_counter = 0;
+ state.unique = true;
+ json_unique_check_init(&state.check);
+
+ uniqueSemAction.semstate = &state;
+ uniqueSemAction.object_start = json_unique_object_start;
+ uniqueSemAction.object_field_start = json_unique_object_field_start;
+ uniqueSemAction.object_end = json_unique_object_end;
+ }
+
+ result = pg_parse_json(lex, check_unique_keys ? &uniqueSemAction : &nullSemAction);
+
+ if (result != JSON_SUCCESS)
+ {
+ if (throw_error)
+ json_ereport_error(result, lex);
+
+ return false; /* invalid json */
+ }
+
+ if (check_unique_keys && !state.unique)
+ {
+ if (throw_error)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE),
+ errmsg("duplicate JSON object key value")));
+
+ return false; /* not unique keys */
+ }
+
+ return true; /* ok */
+}
+
/*
* SQL function json_typeof(json) -> text
*
@@ -1332,21 +1758,13 @@ escape_json(StringInfo buf, const char *str)
Datum
json_typeof(PG_FUNCTION_ARGS)
{
- text *json;
-
- JsonLexContext *lex;
- JsonTokenType tok;
+ text *json = PG_GETARG_TEXT_PP(0);
char *type;
- JsonParseErrorType result;
-
- json = PG_GETARG_TEXT_PP(0);
- lex = makeJsonLexContext(json, false);
+ JsonTokenType tok;
/* Lex exactly one token from the input and check its type. */
- result = json_lex(lex);
- if (result != JSON_SUCCESS)
- json_ereport_error(result, lex);
- tok = lex->token_type;
+ tok = json_get_first_token(json, true);
+
switch (tok)
{
case JSON_TOKEN_OBJECT_START:
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 8d1e7fbf91..d11683c962 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -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"
@@ -33,25 +34,9 @@ typedef struct JsonbInState
{
JsonbParseState *parseState;
JsonbValue *res;
+ bool unique_keys;
} JsonbInState;
-/* unlike with json categories, we need to treat json and jsonb differently */
-typedef enum /* type categories for datum_to_jsonb */
-{
- JSONBTYPE_NULL, /* null, so we didn't bother to identify */
- JSONBTYPE_BOOL, /* boolean (built-in types only) */
- JSONBTYPE_NUMERIC, /* numeric (ditto) */
- JSONBTYPE_DATE, /* we use special formatting for datetimes */
- JSONBTYPE_TIMESTAMP, /* we use special formatting for timestamp */
- JSONBTYPE_TIMESTAMPTZ, /* ... and timestamptz */
- JSONBTYPE_JSON, /* JSON */
- JSONBTYPE_JSONB, /* JSONB */
- JSONBTYPE_ARRAY, /* array */
- JSONBTYPE_COMPOSITE, /* composite */
- JSONBTYPE_JSONCAST, /* something with an explicit cast to JSON */
- JSONBTYPE_OTHER /* all else */
-} JsonbTypeCategory;
-
typedef struct JsonbAggState
{
JsonbInState *res;
@@ -61,7 +46,7 @@ typedef struct JsonbAggState
Oid val_output_func;
} JsonbAggState;
-static inline Datum jsonb_from_cstring(char *json, int len);
+static inline Datum jsonb_from_cstring(char *json, int len, bool unique_keys);
static size_t checkStringLen(size_t len);
static void jsonb_in_object_start(void *pstate);
static void jsonb_in_object_end(void *pstate);
@@ -70,17 +55,11 @@ static void jsonb_in_array_end(void *pstate);
static void jsonb_in_object_field_start(void *pstate, char *fname, bool isnull);
static void jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal);
static void jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype);
-static void jsonb_categorize_type(Oid typoid,
- JsonbTypeCategory *tcategory,
- Oid *outfuncoid);
static void composite_to_jsonb(Datum composite, JsonbInState *result);
static void array_dim_to_jsonb(JsonbInState *result, int dim, int ndims, int *dims,
Datum *vals, bool *nulls, int *valcount,
JsonbTypeCategory tcategory, Oid outfuncoid);
static void array_to_jsonb_internal(Datum array, JsonbInState *result);
-static void jsonb_categorize_type(Oid typoid,
- JsonbTypeCategory *tcategory,
- Oid *outfuncoid);
static void datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
JsonbTypeCategory tcategory, Oid outfuncoid,
bool key_scalar);
@@ -90,6 +69,8 @@ static JsonbParseState *clone_parse_state(JsonbParseState *state);
static char *JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool indent);
static void add_indent(StringInfo out, bool indent, int level);
+int sql_json_type; /* GUC for mapping jsonb to SQL/JSON JSON */
+
/*
* jsonb type input function
*/
@@ -98,7 +79,7 @@ jsonb_in(PG_FUNCTION_ARGS)
{
char *json = PG_GETARG_CSTRING(0);
- return jsonb_from_cstring(json, strlen(json));
+ return jsonb_from_cstring(json, strlen(json), false);
}
/*
@@ -122,7 +103,7 @@ jsonb_recv(PG_FUNCTION_ARGS)
else
elog(ERROR, "unsupported jsonb version number %d", version);
- return jsonb_from_cstring(str, nbytes);
+ return jsonb_from_cstring(str, nbytes, false);
}
/*
@@ -163,6 +144,14 @@ jsonb_send(PG_FUNCTION_ARGS)
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
+Datum
+jsonb_from_text(text *js, bool unique_keys)
+{
+ return jsonb_from_cstring(VARDATA_ANY(js),
+ VARSIZE_ANY_EXHDR(js),
+ unique_keys);
+}
+
/*
* Get the type name of a jsonb container.
*/
@@ -253,7 +242,7 @@ jsonb_typeof(PG_FUNCTION_ARGS)
* Uses the json parser (with hooks) to construct a jsonb.
*/
static inline Datum
-jsonb_from_cstring(char *json, int len)
+jsonb_from_cstring(char *json, int len, bool unique_keys)
{
JsonLexContext *lex;
JsonbInState state;
@@ -263,6 +252,8 @@ jsonb_from_cstring(char *json, int len)
memset(&sem, 0, sizeof(sem));
lex = makeJsonLexContextCstringLen(json, len, GetDatabaseEncoding(), true);
+ state.unique_keys = unique_keys;
+
sem.semstate = (void *) &state;
sem.object_start = jsonb_in_object_start;
@@ -297,6 +288,7 @@ jsonb_in_object_start(void *pstate)
JsonbInState *_state = (JsonbInState *) pstate;
_state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_OBJECT, NULL);
+ _state->parseState->unique_keys = _state->unique_keys;
}
static void
@@ -619,7 +611,7 @@ add_indent(StringInfo out, bool indent, int level)
* output function OID. If the returned category is JSONBTYPE_JSONCAST,
* we return the OID of the relevant cast function instead.
*/
-static void
+void
jsonb_categorize_type(Oid typoid,
JsonbTypeCategory *tcategory,
Oid *outfuncoid)
@@ -1126,6 +1118,51 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result,
datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar);
}
+Datum
+to_jsonb_worker(Datum val, JsonbTypeCategory tcategory, Oid outfuncoid)
+{
+ JsonbInState result;
+
+ memset(&result, 0, sizeof(JsonbInState));
+
+ datum_to_jsonb(val, false, &result, tcategory, outfuncoid, false);
+
+ return JsonbPGetDatum(JsonbValueToJsonb(result.res));
+}
+
+bool
+to_jsonb_is_immutable(Oid typoid)
+{
+ JsonbTypeCategory tcategory;
+ Oid outfuncoid;
+
+ jsonb_categorize_type(typoid, &tcategory, &outfuncoid);
+
+ switch (tcategory)
+ {
+ 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:
+ default:
+ return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE;
+ }
+}
+
/*
* SQL function to_jsonb(anyvalue)
*/
@@ -1134,7 +1171,6 @@ to_jsonb(PG_FUNCTION_ARGS)
{
Datum val = PG_GETARG_DATUM(0);
Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
- JsonbInState result;
JsonbTypeCategory tcategory;
Oid outfuncoid;
@@ -1146,31 +1182,15 @@ to_jsonb(PG_FUNCTION_ARGS)
jsonb_categorize_type(val_type,
&tcategory, &outfuncoid);
- memset(&result, 0, sizeof(JsonbInState));
-
- datum_to_jsonb(val, false, &result, tcategory, outfuncoid, false);
-
- PG_RETURN_POINTER(JsonbValueToJsonb(result.res));
+ PG_RETURN_DATUM(to_jsonb_worker(val, tcategory, outfuncoid));
}
-/*
- * 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,
@@ -1183,15 +1203,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 */
@@ -1200,7 +1231,26 @@ 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));
}
/*
@@ -1219,37 +1269,50 @@ 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.
*/
@@ -1490,6 +1553,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;
@@ -1501,12 +1566,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;
@@ -1554,6 +1615,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);
@@ -1623,6 +1687,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)
{
@@ -1655,11 +1737,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;
@@ -1673,6 +1753,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
*jbval;
JsonbValue v;
JsonbIteratorToken type;
+ bool skip;
if (!AggCheckCallContext(fcinfo, &aggcontext))
{
@@ -1692,6 +1773,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);
@@ -1727,6 +1811,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));
@@ -1782,6 +1875,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;
@@ -1854,6 +1957,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)
{
@@ -2085,3 +2225,65 @@ jsonb_float8(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(retValue);
}
+
+/*
+ * Construct an empty array jsonb.
+ */
+Jsonb *
+JsonbMakeEmptyArray(void)
+{
+ JsonbValue jbv;
+
+ jbv.type = jbvArray;
+ jbv.val.array.elems = NULL;
+ jbv.val.array.nElems = 0;
+ jbv.val.array.rawScalar = false;
+
+ return JsonbValueToJsonb(&jbv);
+}
+
+/*
+ * Construct an empty object jsonb.
+ */
+Jsonb *
+JsonbMakeEmptyObject(void)
+{
+ JsonbValue jbv;
+
+ jbv.type = jbvObject;
+ jbv.val.object.pairs = NULL;
+ jbv.val.object.nPairs = 0;
+
+ return JsonbValueToJsonb(&jbv);
+}
+
+/*
+ * Convert jsonb to a C-string stripping quotes from scalar strings.
+ */
+char *
+JsonbUnquote(Jsonb *jb)
+{
+ if (JB_ROOT_IS_SCALAR(jb))
+ {
+ JsonbValue v;
+
+ JsonbExtractScalar(&jb->root, &v);
+
+ if (v.type == jbvString)
+ return pnstrdup(v.val.string.val, v.val.string.len);
+ else if (v.type == jbvBool)
+ return pstrdup(v.val.boolean ? "true" : "false");
+ else if (v.type == jbvNumeric)
+ return DatumGetCString(DirectFunctionCall1(numeric_out,
+ PointerGetDatum(v.val.numeric)));
+ else if (v.type == jbvNull)
+ return pstrdup("null");
+ else
+ {
+ elog(ERROR, "unrecognized jsonb value type %d", v.type);
+ return NULL;
+ }
+ }
+ else
+ return JsonbToCString(NULL, &jb->root, VARSIZE(jb));
+}
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 5711187795..a4117617d4 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -63,7 +63,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 *arg);
-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);
@@ -688,7 +689,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 */
@@ -731,6 +734,9 @@ pushState(JsonbParseState **pstate)
JsonbParseState *ns = palloc(sizeof(JsonbParseState));
ns->next = *pstate;
+ ns->unique_keys = false;
+ ns->skip_nulls = false;
+
return ns;
}
@@ -1935,7 +1941,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;
@@ -1945,15 +1951,21 @@ 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 value")));
+
+ if (hasNonUniq || skip_nulls)
{
JsonbPair *ptr = object->val.object.pairs + 1,
*res = object->val.object.pairs;
while (ptr - object->val.object.pairs < object->val.object.nPairs)
{
- /* Avoid copying over duplicate */
- if (lengthCompareJsonbStringValue(ptr, res) != 0)
+ /* Avoid copying over duplicate or null */
+ if (lengthCompareJsonbStringValue(ptr, res) != 0 &&
+ (!skip_nulls || ptr->value.type != jbvNull))
{
res++;
if (ptr != res)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 6335845d08..7e1cb032f8 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2771,11 +2771,11 @@ populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */
check_stack_depth();
- if (jbv->type != jbvBinary || !JsonContainerIsArray(jbc))
+ if (jbv->type != jbvBinary ||
+ !JsonContainerIsArray(jbc) ||
+ JsonContainerIsScalar(jbc))
populate_array_report_expected_array(ctx, ndim - 1);
- Assert(!JsonContainerIsScalar(jbc));
-
it = JsonbIteratorInit(jbc);
tok = JsonbIteratorNext(&it, &val, true);
@@ -3247,6 +3247,50 @@ populate_record_field(ColumnIOData *col,
}
}
+/* recursively populate specified type from a json/jsonb value */
+Datum
+json_populate_type(Datum json_val, Oid json_type, Oid typid, int32 typmod,
+ void **cache, MemoryContext mcxt, bool *isnull)
+{
+ JsValue jsv = { 0 };
+ JsonbValue jbv;
+
+ jsv.is_json = json_type == JSONOID;
+
+ if (*isnull)
+ {
+ if (jsv.is_json)
+ jsv.val.json.str = NULL;
+ else
+ jsv.val.jsonb = NULL;
+ }
+ else if (jsv.is_json)
+ {
+ text *json = DatumGetTextPP(json_val);
+
+ jsv.val.json.str = VARDATA_ANY(json);
+ jsv.val.json.len = VARSIZE_ANY_EXHDR(json);
+ jsv.val.json.type = JSON_TOKEN_INVALID; /* not used in populate_composite() */
+ }
+ else
+ {
+ Jsonb *jsonb = DatumGetJsonbP(json_val);
+
+ jsv.val.jsonb = &jbv;
+
+ /* fill binary jsonb value pointing to jb */
+ jbv.type = jbvBinary;
+ jbv.val.binary.data = &jsonb->root;
+ jbv.val.binary.len = VARSIZE(jsonb) - VARHDRSZ;
+ }
+
+ if (!*cache)
+ *cache = MemoryContextAllocZero(mcxt, sizeof(ColumnIOData));
+
+ return populate_record_field(*cache , typid, typmod, NULL, mcxt,
+ PointerGetDatum(NULL), &jsv, isnull);
+}
+
static RecordIOData *
allocate_record_info(MemoryContext mcxt, int ncolumns)
{
@@ -5638,3 +5682,23 @@ transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype
else
appendStringInfoString(_state->strval, token);
}
+
+JsonTokenType
+json_get_first_token(text *json, bool throw_error)
+{
+ JsonLexContext *lex;
+ JsonParseErrorType result;
+
+ lex = makeJsonLexContext(json, false);
+
+ /* Lex exactly one token from the input and check its type. */
+ result = json_lex(lex);
+
+ if (result == JSON_SUCCESS)
+ return lex->token_type;
+
+ if (throw_error)
+ json_ereport_error(result, lex);
+
+ return JSON_TOKEN_INVALID; /* invalid json */
+}
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index fa22546f22..43c9871506 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -67,7 +67,9 @@
#include "lib/stringinfo.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
#include "utils/builtins.h"
+#include "utils/formatting.h"
#include "utils/json.h"
#include "utils/jsonpath.h"
@@ -1073,3 +1075,258 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
return true;
}
+
+/* SQL/JSON datatype status: */
+typedef enum JsonPathDatatypeStatus
+{
+ jpdsNonDateTime, /* null, bool, numeric, string, array, object */
+ jpdsUnknownDateTime, /* unknown datetime type */
+ jpdsDateTimeZoned, /* timetz, timestamptz */
+ jpdsDateTimeNonZoned /* time, timestamp, date */
+} JsonPathDatatypeStatus;
+
+/* Context for jspIsMutableWalker() */
+typedef struct JsonPathMutableContext
+{
+ List *varnames; /* list of variable names */
+ List *varexprs; /* list of variable expressions */
+ JsonPathDatatypeStatus current; /* status of @ item */
+ bool lax; /* jsonpath is lax or strict */
+ bool mutable; /* resulting mutability status */
+} JsonPathMutableContext;
+
+/*
+ * Recursive walker for jspIsMutable()
+ */
+static JsonPathDatatypeStatus
+jspIsMutableWalker(JsonPathItem *jpi, JsonPathMutableContext *cxt)
+{
+ JsonPathItem next;
+ JsonPathDatatypeStatus status = jpdsNonDateTime;
+
+ while (!cxt->mutable)
+ {
+ JsonPathItem arg;
+ JsonPathDatatypeStatus leftStatus;
+ JsonPathDatatypeStatus rightStatus;
+
+ switch (jpi->type)
+ {
+ case jpiRoot:
+ Assert(status == jpdsNonDateTime);
+ break;
+
+ case jpiCurrent:
+ Assert(status == jpdsNonDateTime);
+ status = cxt->current;
+ break;
+
+ case jpiFilter:
+ {
+ JsonPathDatatypeStatus prevStatus = cxt->current;
+
+ cxt->current = status;
+ jspGetArg(jpi, &arg);
+ jspIsMutableWalker(&arg, cxt);
+
+ cxt->current = prevStatus;
+ break;
+ }
+
+ case jpiVariable:
+ {
+ int32 len;
+ const char *name = jspGetString(jpi, &len);
+ ListCell *lc1;
+ ListCell *lc2;
+
+ Assert(status == jpdsNonDateTime);
+
+ forboth(lc1, cxt->varnames, lc2, cxt->varexprs)
+ {
+ String *varname = lfirst_node(String, lc1);
+ Node *varexpr = lfirst(lc2);
+
+ if (strncmp(varname->val, name, len))
+ continue;
+
+ switch (exprType(varexpr))
+ {
+ case DATEOID:
+ case TIMEOID:
+ case TIMESTAMPOID:
+ status = jpdsDateTimeNonZoned;
+ break;
+
+ case TIMETZOID:
+ case TIMESTAMPTZOID:
+ status = jpdsDateTimeZoned;
+ break;
+
+ default:
+ status = jpdsNonDateTime;
+ break;
+ }
+
+ break;
+ }
+ break;
+ }
+
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ Assert(status == jpdsNonDateTime);
+ jspGetLeftArg(jpi, &arg);
+ leftStatus = jspIsMutableWalker(&arg, cxt);
+
+ jspGetRightArg(jpi, &arg);
+ rightStatus = jspIsMutableWalker(&arg, cxt);
+
+ /*
+ * Comparison of datetime type with different timezone status
+ * is mutable.
+ */
+ if (leftStatus != jpdsNonDateTime &&
+ rightStatus != jpdsNonDateTime &&
+ (leftStatus == jpdsUnknownDateTime ||
+ rightStatus == jpdsUnknownDateTime ||
+ leftStatus != rightStatus))
+ cxt->mutable = true;
+ break;
+
+ case jpiNot:
+ case jpiIsUnknown:
+ case jpiExists:
+ case jpiPlus:
+ case jpiMinus:
+ Assert(status == jpdsNonDateTime);
+ jspGetArg(jpi, &arg);
+ jspIsMutableWalker(&arg, cxt);
+ break;
+
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ Assert(status == jpdsNonDateTime);
+ jspGetLeftArg(jpi, &arg);
+ jspIsMutableWalker(&arg, cxt);
+ jspGetRightArg(jpi, &arg);
+ jspIsMutableWalker(&arg, cxt);
+ break;
+
+ case jpiIndexArray:
+ for (int i = 0; i < jpi->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+
+ if (jspGetArraySubscript(jpi, &from, &to, i))
+ jspIsMutableWalker(&to, cxt);
+
+ jspIsMutableWalker(&from, cxt);
+ }
+ /* FALLTHROUGH */
+
+ case jpiAnyArray:
+ if (!cxt->lax)
+ status = jpdsNonDateTime;
+ break;
+
+ case jpiAny:
+ if (jpi->content.anybounds.first > 0)
+ status = jpdsNonDateTime;
+ break;
+
+ case jpiDatetime:
+ if (jpi->content.arg)
+ {
+ char *template;
+ int flags;
+
+ jspGetArg(jpi, &arg);
+ if (arg.type != jpiString)
+ {
+ status = jpdsNonDateTime;
+ break; /* there will be runtime error */
+ }
+
+ template = jspGetString(&arg, NULL);
+ flags = datetime_format_flags(template, NULL);
+ if (flags & DCH_ZONED)
+ status = jpdsDateTimeZoned;
+ else
+ status = jpdsDateTimeNonZoned;
+ }
+ else
+ {
+ status = jpdsUnknownDateTime;
+ }
+ break;
+
+ case jpiLikeRegex:
+ Assert(status == jpdsNonDateTime);
+ jspInitByBuffer(&arg, jpi->base, jpi->content.like_regex.expr);
+ jspIsMutableWalker(&arg, cxt);
+ break;
+
+ /* literals */
+ case jpiNull:
+ case jpiString:
+ case jpiNumeric:
+ case jpiBool:
+ /* accessors */
+ case jpiKey:
+ case jpiAnyKey:
+ /* special items */
+ case jpiSubscript:
+ case jpiLast:
+ /* item methods */
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ status = jpdsNonDateTime;
+ break;
+ }
+
+ if (!jspGetNext(jpi, &next))
+ break;
+
+ jpi = &next;
+ }
+
+ return status;
+}
+
+/*
+ * Check whether jsonpath expression is immutable or not.
+ */
+bool
+jspIsMutable(JsonPath *path, List *varnames, List *varexprs)
+{
+ JsonPathMutableContext cxt;
+ JsonPathItem jpi;
+
+ cxt.varnames = varnames;
+ cxt.varexprs = varexprs;
+ cxt.current = jpdsNonDateTime;
+ cxt.lax = (path->header & JSONPATH_LAX) != 0;
+ cxt.mutable = false;
+
+ jspInit(&jpi, path);
+ jspIsMutableWalker(&jpi, &cxt);
+
+ return cxt.mutable;
+}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 078aaef539..3b596be9fa 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -86,12 +86,16 @@ typedef struct JsonBaseObjectInfo
int id;
} JsonBaseObjectInfo;
+typedef int (*JsonPathVarCallback) (void *vars, char *varName, int varNameLen,
+ JsonbValue *val, JsonbValue *baseObject);
+
/*
* Context of jsonpath execution.
*/
typedef struct JsonPathExecContext
{
- Jsonb *vars; /* variables to substitute into jsonpath */
+ void *vars; /* variables to substitute into jsonpath */
+ JsonPathVarCallback getVar;
JsonbValue *root; /* for $ evaluation */
JsonbValue *current; /* for @ evaluation */
JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue()
@@ -173,7 +177,8 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
void *param);
typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
-static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
+static JsonPathExecResult executeJsonPath(JsonPath *path, void *vars,
+ JsonPathVarCallback getVar,
Jsonb *json, bool throwErrors,
JsonValueList *result, bool useTz);
static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
@@ -225,7 +230,10 @@ static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
JsonbValue *value);
static void getJsonPathVariable(JsonPathExecContext *cxt,
- JsonPathItem *variable, Jsonb *vars, JsonbValue *value);
+ JsonPathItem *variable, JsonbValue *value);
+static int getJsonPathVariableFromJsonb(void *varsJsonb, char *varName,
+ int varNameLen, JsonbValue *val,
+ JsonbValue *baseObject);
static int JsonbArraySize(JsonbValue *jb);
static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
JsonbValue *rv, void *p);
@@ -283,7 +291,8 @@ jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
silent = PG_GETARG_BOOL(3);
}
- res = executeJsonPath(jp, vars, jb, !silent, NULL, tz);
+ res = executeJsonPath(jp, vars, getJsonPathVariableFromJsonb,
+ jb, !silent, NULL, tz);
PG_FREE_IF_COPY(jb, 0);
PG_FREE_IF_COPY(jp, 1);
@@ -338,7 +347,8 @@ jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
silent = PG_GETARG_BOOL(3);
}
- (void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
+ (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb,
+ jb, !silent, &found, tz);
PG_FREE_IF_COPY(jb, 0);
PG_FREE_IF_COPY(jp, 1);
@@ -416,7 +426,8 @@ jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
vars = PG_GETARG_JSONB_P_COPY(2);
silent = PG_GETARG_BOOL(3);
- (void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
+ (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb,
+ jb, !silent, &found, tz);
funcctx->user_fctx = JsonValueListGetList(&found);
@@ -463,7 +474,8 @@ jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
Jsonb *vars = PG_GETARG_JSONB_P(2);
bool silent = PG_GETARG_BOOL(3);
- (void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
+ (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb,
+ jb, !silent, &found, tz);
PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
}
@@ -494,7 +506,8 @@ jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
Jsonb *vars = PG_GETARG_JSONB_P(2);
bool silent = PG_GETARG_BOOL(3);
- (void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
+ (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb,
+ jb, !silent, &found, tz);
if (JsonValueListLength(&found) >= 1)
PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
@@ -536,8 +549,9 @@ jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
* In other case it tries to find all the satisfied result items.
*/
static JsonPathExecResult
-executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
- JsonValueList *result, bool useTz)
+executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar,
+ Jsonb *json, bool throwErrors, JsonValueList *result,
+ bool useTz)
{
JsonPathExecContext cxt;
JsonPathExecResult res;
@@ -549,22 +563,16 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
if (!JsonbExtractScalar(&json->root, &jbv))
JsonbInitBinary(&jbv, json);
- if (vars && !JsonContainerIsObject(&vars->root))
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("\"vars\" argument is not an object"),
- errdetail("Jsonpath parameters should be encoded as key-value pairs of \"vars\" object.")));
- }
-
cxt.vars = vars;
+ cxt.getVar = getVar;
cxt.laxMode = (path->header & JSONPATH_LAX) != 0;
cxt.ignoreStructuralErrors = cxt.laxMode;
cxt.root = &jbv;
cxt.current = &jbv;
cxt.baseObject.jbc = NULL;
cxt.baseObject.id = 0;
- cxt.lastGeneratedObjectId = vars ? 2 : 1;
+ /* 1 + number of base objects in vars */
+ cxt.lastGeneratedObjectId = 1 + getVar(vars, NULL, 0, NULL, NULL);
cxt.innermostArraySize = -1;
cxt.throwErrors = throwErrors;
cxt.useTz = useTz;
@@ -2093,7 +2101,7 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
&value->val.string.len);
break;
case jpiVariable:
- getJsonPathVariable(cxt, item, cxt->vars, value);
+ getJsonPathVariable(cxt, item, value);
return;
default:
elog(ERROR, "unexpected jsonpath item type");
@@ -2105,42 +2113,63 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
*/
static void
getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable,
- Jsonb *vars, JsonbValue *value)
+ JsonbValue *value)
{
char *varName;
int varNameLength;
+ JsonbValue baseObject;
+ int baseObjectId;
+
+ Assert(variable->type == jpiVariable);
+ varName = jspGetString(variable, &varNameLength);
+
+ if (!cxt->vars ||
+ (baseObjectId = cxt->getVar(cxt->vars, varName, varNameLength, value,
+ &baseObject)) < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("could not find jsonpath variable \"%s\"",
+ pnstrdup(varName, varNameLength))));
+
+ if (baseObjectId > 0)
+ setBaseObject(cxt, &baseObject, baseObjectId);
+}
+
+static int
+getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, int varNameLength,
+ JsonbValue *value, JsonbValue *baseObject)
+{
+ Jsonb *vars = varsJsonb;
JsonbValue tmp;
JsonbValue *v;
- if (!vars)
+ if (!varName)
{
- value->type = jbvNull;
- return;
+ if (vars && !JsonContainerIsObject(&vars->root))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"vars\" argument is not an object"),
+ errdetail("Jsonpath parameters should be encoded as key-value pairs of \"vars\" object.")));
+ }
+
+ return vars ? 1 : 0; /* count of base objects */
}
- Assert(variable->type == jpiVariable);
- varName = jspGetString(variable, &varNameLength);
tmp.type = jbvString;
tmp.val.string.val = varName;
tmp.val.string.len = varNameLength;
v = findJsonbValueFromContainer(&vars->root, JB_FOBJECT, &tmp);
- if (v)
- {
- *value = *v;
- pfree(v);
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("could not find jsonpath variable \"%s\"",
- pnstrdup(varName, varNameLength))));
- }
+ if (!v)
+ return -1;
- JsonbInitBinary(&tmp, vars);
- setBaseObject(cxt, &tmp, 1);
+ *value = *v;
+ pfree(v);
+
+ JsonbInitBinary(baseObject, vars);
+ return 1;
}
/**************** Support functions for JsonPath execution *****************/
@@ -2797,3 +2826,244 @@ compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
}
+
+/********************Interface to pgsql's executor***************************/
+
+bool
+JsonPathExists(Datum jb, JsonPath *jp, List *vars, bool *error)
+{
+ JsonPathExecResult res = executeJsonPath(jp, vars, EvalJsonPathVar,
+ DatumGetJsonbP(jb), !error, NULL,
+ true);
+
+ Assert(error || !jperIsError(res));
+
+ if (error && jperIsError(res))
+ *error = true;
+
+ return res == jperOk;
+}
+
+Datum
+JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty,
+ bool *error, List *vars)
+{
+ JsonbValue *first;
+ bool wrap;
+ JsonValueList found = {0};
+ JsonPathExecResult res PG_USED_FOR_ASSERTS_ONLY;
+ int count;
+
+ res = executeJsonPath(jp, vars, EvalJsonPathVar, DatumGetJsonbP(jb), !error,
+ &found, true);
+
+ Assert(error || !jperIsError(res));
+
+ if (error && jperIsError(res))
+ {
+ *error = true;
+ *empty = false;
+ return (Datum) 0;
+ }
+
+ count = JsonValueListLength(&found);
+
+ first = count ? JsonValueListHead(&found) : NULL;
+
+ if (!first)
+ wrap = false;
+ else if (wrapper == JSW_NONE)
+ wrap = false;
+ else if (wrapper == JSW_UNCONDITIONAL)
+ wrap = true;
+ else if (wrapper == JSW_CONDITIONAL)
+ wrap = count > 1 ||
+ IsAJsonbScalar(first) ||
+ (first->type == jbvBinary &&
+ JsonContainerIsScalar(first->val.binary.data));
+ else
+ {
+ elog(ERROR, "unrecognized json wrapper %d", wrapper);
+ wrap = false;
+ }
+
+ if (wrap)
+ return JsonbPGetDatum(JsonbValueToJsonb(wrapItemsInArray(&found)));
+
+ if (count > 1)
+ {
+ if (error)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM),
+ errmsg("JSON path expression in JSON_QUERY should return "
+ "singleton item without wrapper"),
+ errhint("use WITH WRAPPER clause to wrap SQL/JSON item "
+ "sequence into array")));
+ }
+
+ if (first)
+ return JsonbPGetDatum(JsonbValueToJsonb(first));
+
+ *empty = true;
+ return PointerGetDatum(NULL);
+}
+
+JsonbValue *
+JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars)
+{
+ JsonbValue *res;
+ JsonValueList found = { 0 };
+ JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY;
+ int count;
+
+ jper = executeJsonPath(jp, vars, EvalJsonPathVar, DatumGetJsonbP(jb), !error,
+ &found, true);
+
+ Assert(error || !jperIsError(jper));
+
+ if (error && jperIsError(jper))
+ {
+ *error = true;
+ *empty = false;
+ return NULL;
+ }
+
+ count = JsonValueListLength(&found);
+
+ *empty = !count;
+
+ if (*empty)
+ return NULL;
+
+ if (count > 1)
+ {
+ if (error)
+ {
+ *error = true;
+ return NULL;
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM),
+ errmsg("JSON path expression in JSON_VALUE should return "
+ "singleton scalar item")));
+ }
+
+ res = JsonValueListHead(&found);
+
+ if (res->type == jbvBinary &&
+ JsonContainerIsScalar(res->val.binary.data))
+ JsonbExtractScalar(res->val.binary.data, res);
+
+ if (!IsAJsonbScalar(res))
+ {
+ if (error)
+ {
+ *error = true;
+ return NULL;
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_SCALAR_REQUIRED),
+ errmsg("JSON path expression in JSON_VALUE should return "
+ "singleton scalar item")));
+ }
+
+ if (res->type == jbvNull)
+ return NULL;
+
+ return res;
+}
+
+static void
+JsonbValueInitNumericDatum(JsonbValue *jbv, Datum num)
+{
+ jbv->type = jbvNumeric;
+ jbv->val.numeric = DatumGetNumeric(num);
+}
+
+void
+JsonItemFromDatum(Datum val, Oid typid, int32 typmod, JsonbValue *res)
+{
+ switch (typid)
+ {
+ case BOOLOID:
+ res->type = jbvBool;
+ res->val.boolean = DatumGetBool(val);
+ break;
+ case NUMERICOID:
+ JsonbValueInitNumericDatum(res, val);
+ break;
+ case INT2OID:
+ JsonbValueInitNumericDatum(res, DirectFunctionCall1(int2_numeric, val));
+ break;
+ case INT4OID:
+ JsonbValueInitNumericDatum(res, DirectFunctionCall1(int4_numeric, val));
+ break;
+ case INT8OID:
+ JsonbValueInitNumericDatum(res, DirectFunctionCall1(int8_numeric, val));
+ break;
+ case FLOAT4OID:
+ JsonbValueInitNumericDatum(res, DirectFunctionCall1(float4_numeric, val));
+ break;
+ case FLOAT8OID:
+ JsonbValueInitNumericDatum(res, DirectFunctionCall1(float8_numeric, val));
+ break;
+ case TEXTOID:
+ case VARCHAROID:
+ res->type = jbvString;
+ res->val.string.val = VARDATA_ANY(val);
+ res->val.string.len = VARSIZE_ANY_EXHDR(val);
+ break;
+ case DATEOID:
+ case TIMEOID:
+ case TIMETZOID:
+ case TIMESTAMPOID:
+ case TIMESTAMPTZOID:
+ res->type = jbvDatetime;
+ res->val.datetime.value = val;
+ res->val.datetime.typid = typid;
+ res->val.datetime.typmod = typmod;
+ res->val.datetime.tz = 0;
+ break;
+ case JSONBOID:
+ {
+ JsonbValue *jbv = res;
+ Jsonb *jb = DatumGetJsonbP(val);
+
+ if (JsonContainerIsScalar(&jb->root))
+ {
+ bool res PG_USED_FOR_ASSERTS_ONLY;
+
+ res = JsonbExtractScalar(&jb->root, jbv);
+ Assert(res);
+ }
+ else
+ JsonbInitBinary(jbv, jb);
+ break;
+ }
+ case JSONOID:
+ {
+ text *txt = DatumGetTextP(val);
+ char *str = text_to_cstring(txt);
+ Jsonb *jb =
+ DatumGetJsonbP(DirectFunctionCall1(jsonb_in,
+ CStringGetDatum(str)));
+
+ pfree(str);
+
+ JsonItemFromDatum(JsonbPGetDatum(jb), JSONBOID, -1, res);
+ break;
+ }
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("only bool, numeric and text types could be "
+ "casted to supported jsonpath types.")));
+ }
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b932a83827..439f48feb4 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -63,6 +63,7 @@
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/hsearch.h"
+#include "utils/jsonb.h"
#include "utils/lsyscache.h"
#include "utils/partcache.h"
#include "utils/rel.h"
@@ -453,6 +454,12 @@ static void get_coercion_expr(Node *arg, deparse_context *context,
Node *parentNode);
static void get_const_expr(Const *constval, deparse_context *context,
int showtype);
+static void get_json_constructor(JsonConstructorExpr *ctor,
+ deparse_context *context, bool showimplicit);
+static void get_json_agg_constructor(JsonConstructorExpr *ctor,
+ deparse_context *context,
+ const char *funcname,
+ bool is_json_objectagg);
static void get_const_collation(Const *constval, deparse_context *context);
static void simple_quote_literal(StringInfo buf, const char *val);
static void get_sublink_expr(SubLink *sublink, deparse_context *context);
@@ -486,6 +493,8 @@ static char *generate_qualified_type_name(Oid typid);
static text *string_to_text(char *str);
static char *flatten_reloptions(Oid relid);
static void get_reloptions(StringInfo buf, Datum reloptions);
+static void get_json_path_spec(Node *path_spec, deparse_context *context,
+ bool showimplicit);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@@ -6151,7 +6160,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, '(');
@@ -7992,6 +8002,8 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_Aggref:
case T_WindowFunc:
case T_FuncExpr:
+ case T_JsonConstructorExpr:
+ case T_JsonExpr:
/* function-like: name(..) or name[..] */
return true;
@@ -8085,6 +8097,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_NullTest:
case T_BooleanTest:
case T_DistinctExpr:
+ case T_JsonIsPredicate:
switch (nodeTag(parentNode))
{
case T_FuncExpr:
@@ -8108,6 +8121,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_Aggref: /* own parentheses */
case T_WindowFunc: /* own parentheses */
case T_CaseExpr: /* other separators */
+ case T_JsonExpr: /* own parentheses */
return true;
default:
return false;
@@ -8163,6 +8177,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;
}
@@ -8269,6 +8288,121 @@ get_rule_expr_paren(Node *node, deparse_context *context,
}
+/*
+ * get_json_path_spec - Parse back a JSON path specification
+ */
+static void
+get_json_path_spec(Node *path_spec, deparse_context *context, bool showimplicit)
+{
+ if (IsA(path_spec, Const))
+ get_const_expr((Const *) path_spec, context, -1);
+ else
+ get_rule_expr(path_spec, context, showimplicit);
+}
+
+/*
+ * get_json_format - Parse back a JsonFormat node
+ */
+static void
+get_json_format(JsonFormat *format, StringInfo buf)
+{
+ if (format->format == JS_FORMAT_DEFAULT)
+ return;
+
+ appendStringInfoString(buf,
+ format->format == JS_FORMAT_JSONB ?
+ " FORMAT JSONB" : " FORMAT JSON");
+
+ if (format->encoding != JS_ENC_DEFAULT)
+ {
+ const char *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 !=
+ (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON))
+ get_json_format(returning->format, buf);
+}
+
+static void
+get_json_behavior(JsonBehavior *behavior, deparse_context *context,
+ const char *on)
+{
+ /*
+ * The order of array elements must correspond to the order of
+ * JsonBehaviorType members.
+ */
+ const char *behavior_names[] =
+ {
+ " NULL",
+ " ERROR",
+ " EMPTY",
+ " TRUE",
+ " FALSE",
+ " UNKNOWN",
+ " EMPTY ARRAY",
+ " EMPTY OBJECT",
+ " DEFAULT "
+ };
+
+ if ((int) behavior->btype < 0 || behavior->btype >= lengthof(behavior_names))
+ elog(ERROR, "invalid json behavior type: %d", behavior->btype);
+
+ appendStringInfoString(context->buf, behavior_names[behavior->btype]);
+
+ if (behavior->btype == JSON_BEHAVIOR_DEFAULT)
+ get_rule_expr(behavior->default_expr, context, false);
+
+ appendStringInfo(context->buf, " ON %s", on);
+}
+
+/*
+ * get_json_expr_options
+ *
+ * Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS.
+ */
+static void
+get_json_expr_options(JsonExpr *jsexpr, deparse_context *context,
+ JsonBehaviorType default_behavior)
+{
+ if (jsexpr->op == IS_JSON_QUERY)
+ {
+ if (jsexpr->wrapper == JSW_CONDITIONAL)
+ appendStringInfo(context->buf, " WITH CONDITIONAL WRAPPER");
+ else if (jsexpr->wrapper == JSW_UNCONDITIONAL)
+ appendStringInfo(context->buf, " WITH UNCONDITIONAL WRAPPER");
+
+ if (jsexpr->omit_quotes)
+ appendStringInfo(context->buf, " OMIT QUOTES");
+ }
+
+ if (jsexpr->op != IS_JSON_EXISTS &&
+ jsexpr->on_empty->btype != default_behavior)
+ get_json_behavior(jsexpr->on_empty, context, "EMPTY");
+
+ if (jsexpr->on_error->btype != default_behavior)
+ get_json_behavior(jsexpr->on_error, context, "ERROR");
+}
+
/* ----------
* get_rule_expr - Parse back an expression
*
@@ -9444,6 +9578,110 @@ 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_JsonIsPredicate:
+ {
+ JsonIsPredicate *pred = (JsonIsPredicate *) node;
+
+ if (!PRETTY_PAREN(context))
+ appendStringInfoChar(context->buf, '(');
+
+ get_rule_expr_paren(pred->expr, context, true, node);
+
+ appendStringInfoString(context->buf, " IS JSON");
+
+ switch (pred->value_type)
+ {
+ case JS_TYPE_SCALAR:
+ appendStringInfoString(context->buf, " SCALAR");
+ break;
+ case JS_TYPE_ARRAY:
+ appendStringInfoString(context->buf, " ARRAY");
+ break;
+ case JS_TYPE_OBJECT:
+ appendStringInfoString(context->buf, " OBJECT");
+ break;
+ default:
+ break;
+ }
+
+ if (pred->unique_keys)
+ appendStringInfoString(context->buf, " WITH UNIQUE KEYS");
+
+ if (!PRETTY_PAREN(context))
+ appendStringInfoChar(context->buf, ')');
+ }
+ break;
+
+ case T_JsonExpr:
+ {
+ JsonExpr *jexpr = (JsonExpr *) node;
+
+ switch (jexpr->op)
+ {
+ case IS_JSON_QUERY:
+ appendStringInfoString(buf, "JSON_QUERY(");
+ break;
+ case IS_JSON_VALUE:
+ appendStringInfoString(buf, "JSON_VALUE(");
+ break;
+ case IS_JSON_EXISTS:
+ appendStringInfoString(buf, "JSON_EXISTS(");
+ break;
+ }
+
+ get_rule_expr(jexpr->formatted_expr, context, showimplicit);
+
+ appendStringInfoString(buf, ", ");
+
+ get_json_path_spec(jexpr->path_spec, context, showimplicit);
+
+ if (jexpr->passing_values)
+ {
+ ListCell *lc1, *lc2;
+ bool needcomma = false;
+
+ appendStringInfoString(buf, " PASSING ");
+
+ forboth(lc1, jexpr->passing_names,
+ lc2, jexpr->passing_values)
+ {
+ if (needcomma)
+ appendStringInfoString(buf, ", ");
+ needcomma = true;
+
+ get_rule_expr((Node *) lfirst(lc2), context, showimplicit);
+ appendStringInfo(buf, " AS %s",
+ ((String *) lfirst_node(String, lc1))->val);
+ }
+ }
+
+ if (jexpr->op != IS_JSON_EXISTS ||
+ jexpr->returning->typid != BOOLOID)
+ get_json_returning(jexpr->returning, context->buf,
+ jexpr->op == IS_JSON_QUERY);
+
+ get_json_expr_options(jexpr, context,
+ jexpr->op == IS_JSON_EXISTS ?
+ JSON_BEHAVIOR_FALSE : JSON_BEHAVIOR_NULL);
+
+ appendStringInfoString(buf, ")");
+ }
+ break;
+
case T_List:
{
char *sep;
@@ -9541,6 +9779,7 @@ looks_like_function(Node *node)
case T_MinMaxExpr:
case T_SQLValueFunction:
case T_XmlExpr:
+ case T_JsonExpr:
/* these are all accepted by func_expr_common_subexpr */
return true;
default:
@@ -9686,17 +9925,101 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
appendStringInfoChar(buf, ')');
}
+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");
+
+ if (!((ctor->type == JSCTOR_JSON_PARSE ||
+ ctor->type == JSCTOR_JSON_SCALAR) &&
+ ctor->returning->typid == SQLJSON_TYPE_OID()))
+ get_json_returning(ctor->returning, buf, true);
+}
+
+static void
+get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context,
+ bool showimplicit)
+{
+ StringInfo buf = context->buf;
+ const char *funcname;
+ int nargs;
+ ListCell *lc;
+
+ switch (ctor->type)
+ {
+ case JSCTOR_JSON_PARSE:
+ funcname = "JSON";
+ break;
+ case JSCTOR_JSON_SCALAR:
+ funcname = "JSON_SCALAR";
+ break;
+ case JSCTOR_JSON_SERIALIZE:
+ funcname = "JSON_SERIALIZE";
+ break;
+ case JSCTOR_JSON_OBJECT:
+ funcname = "JSON_OBJECT";
+ break;
+ case JSCTOR_JSON_ARRAY:
+ funcname = "JSON_ARRAY";
+ break;
+ case JSCTOR_JSON_OBJECTAGG:
+ return get_json_agg_constructor(ctor, context, "JSON_OBJECTAGG", true);
+ case JSCTOR_JSON_ARRAYAGG:
+ return get_json_agg_constructor(ctor, context, "JSON_ARRAYAGG", false);
+ default:
+ elog(ERROR, "invalid JsonConstructorExprType %d", ctor->type);
+ }
+
+ appendStringInfo(buf, "%s(", funcname);
+
+ nargs = 0;
+ foreach(lc, ctor->args)
+ {
+ if (nargs > 0)
+ {
+ const char *sep = ctor->type == JSCTOR_JSON_OBJECT &&
+ (nargs % 2) != 0 ? " : " : ", ";
+
+ appendStringInfoString(buf, sep);
+ }
+
+ get_rule_expr((Node *) lfirst(lc), context, true);
+
+ nargs++;
+ }
+
+ get_json_constructor_options(ctor, buf);
+
+ appendStringInfo(buf, ")");
+}
+
+
/*
- * get_agg_expr - Parse back an Aggref node
+ * get_agg_expr_helper - Parse back an Aggref node
*/
static void
-get_agg_expr(Aggref *aggref, deparse_context *context,
- Aggref *original_aggref)
+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
@@ -9726,13 +10049,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))
@@ -9768,7 +10092,17 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
if (tle->resjunk)
continue;
if (i++ > 0)
- appendStringInfoString(buf, ", ");
+ {
+ if (is_json_objectagg)
+ {
+ if (i > 2)
+ break; /* skip ABSENT ON NULL and WITH UNIQUE args */
+
+ appendStringInfoString(buf, " : ");
+ }
+ else
+ appendStringInfoString(buf, ", ");
+ }
if (use_variadic && i == nargs)
appendStringInfoString(buf, "VARIADIC ");
get_rule_expr(arg, context, true);
@@ -9782,6 +10116,9 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
}
}
+ if (options)
+ appendStringInfoString(buf, options);
+
if (aggref->aggfilter != NULL)
{
appendStringInfoString(buf, ") FILTER (WHERE ");
@@ -9791,6 +10128,16 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
appendStringInfoChar(buf, ')');
}
+/*
+ * get_agg_expr - Parse back an Aggref node
+ */
+static void
+get_agg_expr(Aggref *aggref, deparse_context *context, Aggref *original_aggref)
+{
+ return get_agg_expr_helper(aggref, context, original_aggref, NULL, NULL,
+ false);
+}
+
/*
* This is a helper function for get_agg_expr(). It's used when we deparse
* a combining Aggref; resolve_special_varno locates the corresponding partial
@@ -9810,10 +10157,12 @@ get_agg_combine_expr(Node *node, deparse_context *context, void *callback_arg)
}
/*
- * get_windowfunc_expr - Parse back a WindowFunc node
+ * get_windowfunc_expr_helper - Parse back a WindowFunc node
*/
static void
-get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
+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];
@@ -9837,16 +10186,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)
{
@@ -9883,6 +10246,15 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
}
}
+/*
+ * get_windowfunc_expr - Parse back a WindowFunc node
+ */
+static void
+get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
+{
+ return get_windowfunc_expr_helper(wfunc, context, NULL, NULL, false);
+}
+
/*
* get_func_sql_syntax - Parse back a SQL-syntax function call
*
@@ -10123,6 +10495,31 @@ get_func_sql_syntax(FuncExpr *expr, deparse_context *context)
return false;
}
+/*
+ * 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))
+ return get_agg_expr_helper((Aggref *) ctor->func, context,
+ (Aggref *) ctor->func,
+ funcname, options.data, is_json_objectagg);
+ else if (IsA(ctor->func, WindowFunc))
+ return 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));
+}
+
/* ----------
* get_coercion_expr
*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a6e4fcc24e..f1a0327148 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -95,6 +95,7 @@
#include "utils/bytea.h"
#include "utils/float.h"
#include "utils/guc_tables.h"
+#include "utils/jsonb.h"
#include "utils/memutils.h"
#include "utils/pg_locale.h"
#include "utils/pg_lsn.h"
@@ -556,6 +557,12 @@ static const struct config_enum_entry wal_compression_options[] = {
{NULL, 0, false}
};
+const struct config_enum_entry sql_json_type_info[] = {
+ {"json", SQLJSON_TYPE_JSON, false},
+ {"jsonb", SQLJSON_TYPE_JSONB, false},
+ {NULL, 0, false}
+};
+
/*
* Options for enum values stored in other modules
*/
@@ -4982,6 +4989,18 @@ static struct config_enum ConfigureNamesEnum[] =
NULL, NULL, NULL
},
+ {
+ {"sql_json", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
+ gettext_noop("Sets what PostgreSQL type to use as an implementaion of SQL JSON type."),
+ gettext_noop("When turned on, jsonb type is mapped to SQL JSON type, "
+ "json type is mapped to JSON TEXT type.")
+ },
+ &sql_json_type,
+ SQLJSON_TYPE_JSON,
+ sql_json_type_info,
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 3fe9a53cb3..3292846f89 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -762,6 +762,7 @@
# - Other Platforms and Clients -
#transform_null_equals = off
+#sql_json = json # jsonb
#------------------------------------------------------------------------------
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index 9f2cd1f127..9b1252f06d 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -737,6 +737,75 @@ JumbleExpr(JumbleState *jstate, Node *node)
JumbleExpr(jstate, (Node *) conf->exclRelTlist);
}
break;
+ case T_JsonFormat:
+ {
+ JsonFormat *format = (JsonFormat *) node;
+
+ APP_JUMB(format->type);
+ APP_JUMB(format->encoding);
+ }
+ break;
+ case T_JsonReturning:
+ {
+ JsonReturning *returning = (JsonReturning *) node;
+
+ JumbleExpr(jstate, (Node *) returning->format);
+ APP_JUMB(returning->typid);
+ APP_JUMB(returning->typmod);
+ }
+ break;
+ case T_JsonValueExpr:
+ {
+ JsonValueExpr *expr = (JsonValueExpr *) node;
+
+ JumbleExpr(jstate, (Node *) expr->raw_expr);
+ JumbleExpr(jstate, (Node *) expr->formatted_expr);
+ JumbleExpr(jstate, (Node *) expr->format);
+ }
+ break;
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+
+ JumbleExpr(jstate, (Node *) ctor->func);
+ JumbleExpr(jstate, (Node *) ctor->coercion);
+ JumbleExpr(jstate, (Node *) ctor->returning);
+ APP_JUMB(ctor->type);
+ APP_JUMB(ctor->unique);
+ APP_JUMB(ctor->absent_on_null);
+ }
+ break;
+ case T_JsonIsPredicate:
+ {
+ JsonIsPredicate *pred = (JsonIsPredicate *) node;
+
+ JumbleExpr(jstate, (Node *) pred->expr);
+ JumbleExpr(jstate, (Node *) pred->format);
+ APP_JUMB(pred->unique_keys);
+ APP_JUMB(pred->value_type);
+ }
+ break;
+ case T_JsonExpr:
+ {
+ JsonExpr *jexpr = (JsonExpr *) node;
+
+ APP_JUMB(jexpr->op);
+ JumbleExpr(jstate, jexpr->formatted_expr);
+ JumbleExpr(jstate, jexpr->path_spec);
+ foreach(temp, jexpr->passing_names)
+ {
+ APP_JUMB_STRING(lfirst_node(String, temp)->val);
+ }
+ JumbleExpr(jstate, (Node *) jexpr->passing_values);
+ if (jexpr->on_empty)
+ {
+ APP_JUMB(jexpr->on_empty->btype);
+ JumbleExpr(jstate, jexpr->on_empty->default_expr);
+ }
+ APP_JUMB(jexpr->on_error->btype);
+ JumbleExpr(jstate, jexpr->on_error->default_expr);
+ }
+ break;
case T_List:
foreach(temp, (List *) node)
{
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index fc6d3bfd94..aaf6e20e0b 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -561,14 +561,36 @@
# 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',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d068d6532e..50a773009f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8722,6 +8722,10 @@
proname => 'json_agg_transfn', proisstrict => 'f', provolatile => 's',
prorettype => 'internal', proargtypes => 'internal anyelement',
prosrc => 'json_agg_transfn' },
+{ oid => '8173', 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' },
@@ -8729,10 +8733,26 @@
proname => 'json_agg', prokind => 'a', proisstrict => 'f', provolatile => 's',
prorettype => 'json', proargtypes => 'anyelement',
prosrc => 'aggregate_dummy' },
+{ oid => '8174', 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 => '8175', 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 => '8176', 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 => '8177', 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',
@@ -8741,6 +8761,19 @@
proname => 'json_object_agg', prokind => 'a', proisstrict => 'f',
provolatile => 's', prorettype => 'json', proargtypes => 'any any',
prosrc => 'aggregate_dummy' },
+{ oid => '8178', 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 => '8179', 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 => '8180',
+ 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',
@@ -9613,6 +9646,10 @@
proname => 'jsonb_agg_transfn', proisstrict => 'f', provolatile => 's',
prorettype => 'internal', proargtypes => 'internal anyelement',
prosrc => 'jsonb_agg_transfn' },
+{ oid => '8181', 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',
@@ -9621,10 +9658,29 @@
proname => 'jsonb_agg', prokind => 'a', proisstrict => 'f',
provolatile => 's', prorettype => 'jsonb', proargtypes => 'anyelement',
prosrc => 'aggregate_dummy' },
+{ oid => '8182', 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 => '8183', 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 => '8184', 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 => '8185', 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',
@@ -9633,6 +9689,20 @@
proname => 'jsonb_object_agg', prokind => 'a', proisstrict => 'f',
prorettype => 'jsonb', proargtypes => 'any any',
prosrc => 'aggregate_dummy' },
+{ oid => '8186', 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 => '8187',
+ 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 => '8188',
+ 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',
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 6a24341faa..00916724d9 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -21,6 +21,7 @@
struct ExprEvalStep;
struct SubscriptingRefState;
struct ScalarArrayOpExprHashTable;
+struct JsonbValue;
/* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
/* expression's interpreter has been initialized */
@@ -239,6 +240,9 @@ typedef enum ExprEvalOp
EEOP_GROUPING_FUNC,
EEOP_WINDOW_FUNC,
EEOP_SUBPLAN,
+ EEOP_JSON_CONSTRUCTOR,
+ EEOP_IS_JSON,
+ EEOP_JSONEXPR,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -668,6 +672,72 @@ typedef struct ExprEvalStep
int transno;
int setoff;
} agg_trans;
+
+ /* for EEOP_JSON_CONSTRUCTOR */
+ struct
+ {
+ 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;
+ } json_constructor;
+
+ /* for EEOP_IS_JSON */
+ struct
+ {
+ JsonIsPredicate *pred; /* original expression node */
+ } is_json;
+
+ /* for EEOP_JSONEXPR */
+ struct
+ {
+ JsonExpr *jsexpr; /* original expression node */
+
+ struct
+ {
+ FmgrInfo func; /* typinput function for output type */
+ Oid typioparam;
+ } input; /* I/O info for output type */
+
+ NullableDatum
+ *formatted_expr, /* formatted context item value */
+ *res_expr, /* result item */
+ *coercion_expr, /* input for JSON item coercion */
+ *pathspec; /* path specification value */
+
+ ExprState *result_expr; /* coerced to output type */
+ ExprState *default_on_empty; /* ON EMPTY DEFAULT expression */
+ ExprState *default_on_error; /* ON ERROR DEFAULT expression */
+ List *args; /* passing arguments */
+
+ void *cache; /* cache for json_populate_type() */
+
+ struct JsonCoercionsState
+ {
+ struct JsonCoercionState
+ {
+ JsonCoercion *coercion; /* coercion expression */
+ ExprState *estate; /* coercion expression state */
+ } null,
+ string,
+ numeric,
+ boolean,
+ date,
+ time,
+ timetz,
+ timestamp,
+ timestamptz,
+ composite;
+ } coercions; /* states for coercion from SQL/JSON item
+ * types directly to the output type */
+ } jsonexpr;
+
} d;
} ExprEvalStep;
@@ -762,6 +832,7 @@ extern void ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op,
extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op);
extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op);
extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
@@ -769,6 +840,16 @@ 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 ExecEvalJson(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item,
+ JsonReturning *returning,
+ struct JsonCoercionsState *coercions,
+ struct JsonCoercionState **pjcstate);
+extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
+ struct JsonCoercionsState *);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index cd57a704ad..26a4bd28ee 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -263,6 +263,8 @@ ExecProcNode(PlanState *node)
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
+extern ExprState *ExecInitExprWithCaseValue(Expr *node, PlanState *parent,
+ Datum *caseval, bool *casenull);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
extern List *ExecInitExprList(List *nodes, PlanState *parent);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index eea87f847d..c1cfbfc6b3 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -106,4 +106,14 @@ 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 JsonBehavior *makeJsonBehavior(JsonBehaviorType type, Node *expr);
+extern Node *makeJsonKeyValue(Node *key, Node *value);
+extern Node *makeJsonIsPredicate(Node *expr, JsonFormat *format,
+ JsonValueType vtype, bool unique_keys,
+ int location);
+extern JsonEncoding makeJsonEncoding(char *name);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..5edf7aecee 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -201,6 +201,16 @@ typedef enum NodeTag
T_FromExpr,
T_OnConflictExpr,
T_IntoClause,
+ T_JsonFormat,
+ T_JsonReturning,
+ T_JsonValueExpr,
+ T_JsonParseExpr,
+ T_JsonScalarExpr,
+ T_JsonSerializeExpr,
+ T_JsonConstructorExpr,
+ T_JsonExpr,
+ T_JsonCoercion,
+ T_JsonItemCoercions,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -488,6 +498,20 @@ typedef enum NodeTag
T_PartitionCmd,
T_VacuumRelation,
T_PublicationTable,
+ T_JsonObjectConstructor,
+ T_JsonArrayConstructor,
+ T_JsonArrayQueryConstructor,
+ T_JsonAggConstructor,
+ T_JsonObjectAgg,
+ T_JsonArrayAgg,
+ T_JsonFuncExpr,
+ T_JsonIsPredicate,
+ T_JsonExistsPredicate,
+ T_JsonCommon,
+ T_JsonArgument,
+ T_JsonKeyValue,
+ T_JsonBehavior,
+ T_JsonOutput,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..f8390ee9c0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1550,6 +1550,209 @@ typedef struct TriggerTransition
bool isTable;
} TriggerTransition;
+/* Nodes for SQL/JSON support */
+
+/*
+ * JsonQuotes -
+ * representation of [KEEP|OMIT] QUOTES clause for JSON_QUERY()
+ */
+typedef enum JsonQuotes
+{
+ JS_QUOTES_UNSPEC, /* unspecified */
+ JS_QUOTES_KEEP, /* KEEP QUOTES */
+ JS_QUOTES_OMIT /* OMIT QUOTES */
+} JsonQuotes;
+
+/*
+ * JsonPathSpec -
+ * representation of JSON path constant
+ */
+typedef char *JsonPathSpec;
+
+/*
+ * 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;
+
+/*
+ * JsonArgument -
+ * representation of argument from JSON PASSING clause
+ */
+typedef struct JsonArgument
+{
+ NodeTag type;
+ JsonValueExpr *val; /* argument value expression */
+ char *name; /* argument name */
+} JsonArgument;
+
+/*
+ * JsonCommon -
+ * representation of common syntax of functions using JSON path
+ */
+typedef struct JsonCommon
+{
+ NodeTag type;
+ JsonValueExpr *expr; /* context item expression */
+ Node *pathspec; /* JSON path specification expression */
+ char *pathname; /* path name, if any */
+ List *passing; /* list of PASSING clause arguments, if any */
+ int location; /* token location, or -1 if unknown */
+} JsonCommon;
+
+/*
+ * JsonFuncExpr -
+ * untransformed representation of JSON function expressions
+ */
+typedef struct JsonFuncExpr
+{
+ NodeTag type;
+ JsonExprOp op; /* expression type */
+ JsonCommon *common; /* common syntax */
+ JsonOutput *output; /* output clause, if specified */
+ JsonBehavior *on_empty; /* ON EMPTY behavior, if specified */
+ JsonBehavior *on_error; /* ON ERROR behavior, if specified */
+ JsonWrapper wrapper; /* array wrapper behavior (JSON_QUERY only) */
+ bool omit_quotes; /* omit or keep quotes? (JSON_QUERY only) */
+ int location; /* token location, or -1 if unknown */
+} JsonFuncExpr;
+
+/*
+ * 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;
+
+/*
+ * JsonParseExpr -
+ * untransformed representation of JSON()
+ */
+typedef struct JsonParseExpr
+{
+ NodeTag type;
+ JsonValueExpr *expr; /* string expression */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ bool unique_keys; /* WITH UNIQUE KEYS? */
+ int location; /* token location, or -1 if unknown */
+} JsonParseExpr;
+
+/*
+ * JsonScalarExpr -
+ * untransformed representation of JSON_SCALAR()
+ */
+typedef struct JsonScalarExpr
+{
+ NodeTag type;
+ Expr *expr; /* scalar expression */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ int location; /* token location, or -1 if unknown */
+} JsonScalarExpr;
+
+/*
+ * JsonSerializeExpr -
+ * untransformed representation of JSON_SERIALIZE() function
+ */
+typedef struct JsonSerializeExpr
+{
+ NodeTag type;
+ JsonValueExpr *expr; /* json value expression */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ int location; /* token location, or -1 if unknown */
+} JsonSerializeExpr;
+
+/*
+ * JsonObjectConstructor -
+ * untransformed representation of JSON_OBJECT() constructor
+ */
+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
*****************************************************************************/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 433437643e..0af3d0bb2f 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1235,6 +1235,228 @@ typedef struct XmlExpr
int location; /* token location, or -1 if unknown */
} XmlExpr;
+/*
+ * JsonExprOp -
+ * enumeration of JSON functions using JSON path
+ */
+typedef enum JsonExprOp
+{
+ IS_JSON_VALUE, /* JSON_VALUE() */
+ IS_JSON_QUERY, /* JSON_QUERY() */
+ IS_JSON_EXISTS /* JSON_EXISTS() */
+} JsonExprOp;
+
+/*
+ * 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;
+
+/*
+ * JsonBehaviorType -
+ * enumeration of behavior types used in JSON ON ... BEHAVIOR clause
+ *
+ * If enum members are reordered, get_json_behavior() from ruleutils.c
+ * must be updated accordingly.
+ */
+typedef enum JsonBehaviorType
+{
+ JSON_BEHAVIOR_NULL = 0,
+ JSON_BEHAVIOR_ERROR,
+ JSON_BEHAVIOR_EMPTY,
+ JSON_BEHAVIOR_TRUE,
+ JSON_BEHAVIOR_FALSE,
+ JSON_BEHAVIOR_UNKNOWN,
+ JSON_BEHAVIOR_EMPTY_ARRAY,
+ JSON_BEHAVIOR_EMPTY_OBJECT,
+ JSON_BEHAVIOR_DEFAULT
+} JsonBehaviorType;
+
+/*
+ * JsonWrapper -
+ * representation of WRAPPER clause for JSON_QUERY()
+ */
+typedef enum JsonWrapper
+{
+ JSW_NONE,
+ JSW_CONDITIONAL,
+ JSW_UNCONDITIONAL,
+} JsonWrapper;
+
+/*
+ * JsonFormat -
+ * representation of JSON FORMAT clause
+ */
+typedef struct JsonFormat
+{
+ NodeTag type;
+ JsonFormatType format; /* 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,
+ JSCTOR_JSON_SCALAR = 5,
+ JSCTOR_JSON_SERIALIZE = 6,
+ JSCTOR_JSON_PARSE = 7
+} 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;
+
+/*
+ * JsonValueType -
+ * representation of JSON item type in IS JSON predicate
+ */
+typedef enum JsonValueType
+{
+ JS_TYPE_ANY, /* IS JSON [VALUE] */
+ JS_TYPE_OBJECT, /* IS JSON OBJECT */
+ JS_TYPE_ARRAY, /* IS JSON ARRAY*/
+ JS_TYPE_SCALAR /* IS JSON SCALAR */
+} JsonValueType;
+
+/*
+ * JsonIsPredicate -
+ * untransformed representation of IS JSON predicate
+ */
+typedef struct JsonIsPredicate
+{
+ NodeTag type;
+ Node *expr; /* untransformed expression */
+ JsonFormat *format; /* FORMAT clause, if specified */
+ JsonValueType value_type; /* JSON item type */
+ bool unique_keys; /* check key uniqueness? */
+ int location; /* token location, or -1 if unknown */
+} JsonIsPredicate;
+
+/*
+ * JsonBehavior -
+ * representation of JSON ON ... BEHAVIOR clause
+ */
+typedef struct JsonBehavior
+{
+ NodeTag type;
+ JsonBehaviorType btype; /* behavior type */
+ Node *default_expr; /* default expression, if any */
+} JsonBehavior;
+
+/*
+ * JsonCoercion -
+ * coercion from SQL/JSON item types to SQL types
+ */
+typedef struct JsonCoercion
+{
+ NodeTag type;
+ Node *expr; /* resulting expression coerced to target type */
+ bool via_populate; /* coerce result using json_populate_type()? */
+ bool via_io; /* coerce result using type input function? */
+ Oid collation; /* collation for coercion via I/O or populate */
+} JsonCoercion;
+
+/*
+ * JsonItemCoercions -
+ * expressions for coercion from SQL/JSON item types directly to the
+ * output SQL type
+ */
+typedef struct JsonItemCoercions
+{
+ NodeTag type;
+ JsonCoercion *null;
+ JsonCoercion *string;
+ JsonCoercion *numeric;
+ JsonCoercion *boolean;
+ JsonCoercion *date;
+ JsonCoercion *time;
+ JsonCoercion *timetz;
+ JsonCoercion *timestamp;
+ JsonCoercion *timestamptz;
+ JsonCoercion *composite; /* arrays and objects */
+} JsonItemCoercions;
+
+/*
+ * JsonExpr -
+ * transformed representation of JSON_VALUE(), JSON_QUERY(), JSON_EXISTS()
+ */
+typedef struct JsonExpr
+{
+ Expr xpr;
+ JsonExprOp op; /* json function ID */
+ Node *formatted_expr; /* formatted context item expression */
+ JsonCoercion *result_coercion; /* resulting coercion to RETURNING type */
+ JsonFormat *format; /* context item format (JSON/JSONB) */
+ Node *path_spec; /* JSON path specification expression */
+ List *passing_names; /* PASSING argument names */
+ List *passing_values; /* PASSING argument values */
+ JsonReturning *returning; /* RETURNING clause type/format info */
+ JsonBehavior *on_empty; /* ON EMPTY behavior */
+ JsonBehavior *on_error; /* ON ERROR behavior */
+ JsonItemCoercions *coercions; /* coercions for JSON_VALUE */
+ JsonWrapper wrapper; /* WRAPPER for JSON_QUERY */
+ bool omit_quotes; /* KEEP/OMIT QUOTES for JSON_QUERY */
+ int location; /* token location, or -1 if unknown */
+} JsonExpr;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..458f55fbec 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
@@ -92,6 +93,7 @@ PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("compression", COMPRESSION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("conditional", CONDITIONAL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -146,11 +148,13 @@ PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("else", ELSE, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("empty", EMPTY_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("end", END_P, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("error", ERROR_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("event", EVENT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD, AS_LABEL)
@@ -175,6 +179,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)
@@ -227,7 +232,19 @@ 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, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_exists", JSON_EXISTS, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_query", JSON_QUERY, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_scalar", JSON_SCALAR, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_serialize", JSON_SERIALIZE, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_value", JSON_VALUE, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("keep", KEEP, UNRESERVED_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)
@@ -289,6 +306,7 @@ PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("oids", OIDS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("old", OLD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("omit", OMIT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("on", ON, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("only", ONLY, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("operator", OPERATOR, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -330,6 +348,7 @@ PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("quotes", QUOTES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("read", READ, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("real", REAL, COL_NAME_KEYWORD, BARE_LABEL)
@@ -364,6 +383,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("scalar", SCALAR, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("schemas", SCHEMAS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -399,6 +419,7 @@ PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("string", STRING, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD, BARE_LABEL)
@@ -433,6 +454,7 @@ PG_KEYWORD("types", TYPES_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("uescape", UESCAPE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("unbounded", UNBOUNDED, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("uncommitted", UNCOMMITTED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("unconditional", UNCONDITIONAL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("unencrypted", UNENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("union", UNION, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("unique", UNIQUE, RESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index 93f979f320..c20a6a6d5f 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -17,6 +17,9 @@
#ifndef _FORMATTING_H_
#define _FORMATTING_H_
+#define DCH_DATED 0x01
+#define DCH_TIMED 0x02
+#define DCH_ZONED 0x04
extern char *str_tolower(const char *buff, size_t nbytes, Oid collid);
extern char *str_toupper(const char *buff, size_t nbytes, Oid collid);
@@ -29,5 +32,6 @@ extern char *asc_initcap(const char *buff, size_t nbytes);
extern Datum parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict,
Oid *typid, int32 *typmod, int *tz,
bool *have_error);
+extern int datetime_format_flags(const char *fmt_str, bool *have_error);
#endif
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index 7daf09f20e..f16cfdcb71 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -16,9 +16,35 @@
#include "lib/stringinfo.h"
+typedef enum /* type categories for datum_to_json */
+{
+ JSONTYPE_NULL, /* null, so we didn't bother to identify */
+ JSONTYPE_BOOL, /* boolean (built-in types only) */
+ JSONTYPE_NUMERIC, /* numeric (ditto) */
+ JSONTYPE_DATE, /* we use special formatting for datetimes */
+ JSONTYPE_TIMESTAMP,
+ JSONTYPE_TIMESTAMPTZ,
+ JSONTYPE_JSON, /* JSON itself (and JSONB) */
+ JSONTYPE_ARRAY, /* array */
+ JSONTYPE_COMPOSITE, /* composite */
+ JSONTYPE_CAST, /* something with an explicit cast to JSON */
+ JSONTYPE_OTHER /* all else */
+} JsonTypeCategory;
+
/* functions in json.c */
extern void escape_json(StringInfo buf, const char *str);
extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
const int *tzp);
+extern bool to_json_is_immutable(Oid typoid);
+extern void json_categorize_type(Oid typoid, JsonTypeCategory *tcategory,
+ Oid *outfuncoid);
+extern Datum to_json_worker(Datum val, JsonTypeCategory tcategory,
+ Oid outfuncoid);
+extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null,
+ bool unique_keys);
+extern Datum json_build_array_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null);
+extern bool json_validate(text *json, bool check_unique_keys, bool throw_error);
#endif /* JSON_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 4e07debf78..be5cc0e397 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -329,6 +329,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;
/*
@@ -374,6 +376,36 @@ typedef struct JsonbIterator
struct JsonbIterator *parent;
} JsonbIterator;
+/* unlike with json categories, we need to treat json and jsonb differently */
+typedef enum /* type categories for datum_to_jsonb */
+{
+ JSONBTYPE_NULL, /* null, so we didn't bother to identify */
+ JSONBTYPE_BOOL, /* boolean (built-in types only) */
+ JSONBTYPE_NUMERIC, /* numeric (ditto) */
+ JSONBTYPE_DATE, /* we use special formatting for datetimes */
+ JSONBTYPE_TIMESTAMP, /* we use special formatting for timestamp */
+ JSONBTYPE_TIMESTAMPTZ, /* ... and timestamptz */
+ JSONBTYPE_JSON, /* JSON */
+ JSONBTYPE_JSONB, /* JSONB */
+ JSONBTYPE_ARRAY, /* array */
+ JSONBTYPE_COMPOSITE, /* composite */
+ JSONBTYPE_JSONCAST, /* something with an explicit cast to JSON */
+ JSONBTYPE_OTHER /* all else */
+} JsonbTypeCategory;
+
+/* values for the sql+json_type GUC. */
+typedef enum SqlJsonType
+{
+ SQLJSON_TYPE_JSON = 0,
+ SQLJSON_TYPE_JSONB = 1
+} SqlJsonType;
+
+#define SQLJSON_TYPE_IS_JSONB() (sql_json_type == SQLJSON_TYPE_JSONB)
+#define SQLJSON_TYPE_OID() (SQLJSON_TYPE_IS_JSONB() ? JSONBOID : JSONOID)
+#define SQLJSON_TYPE_NAME() (SQLJSON_TYPE_IS_JSONB() ? "jsonb" : "json")
+
+/* GUC */
+extern int sql_json_type;
/* Support functions */
extern uint32 getJsonbOffset(const JsonbContainer *jc, int index);
@@ -401,10 +433,14 @@ extern void JsonbHashScalarValueExtended(const JsonbValue *scalarVal,
uint64 *hash, uint64 seed);
/* jsonb.c support functions */
+extern Datum jsonb_from_text(text *js, bool unique_keys);
extern char *JsonbToCString(StringInfo out, JsonbContainer *in,
int estimated_len);
extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in,
int estimated_len);
+extern Jsonb *JsonbMakeEmptyArray(void);
+extern Jsonb *JsonbMakeEmptyObject(void);
+extern char *JsonbUnquote(Jsonb *jb);
extern bool JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res);
extern const char *JsonbTypeName(JsonbValue *jb);
@@ -412,4 +448,15 @@ 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 void jsonb_categorize_type(Oid typoid, JsonbTypeCategory *tcategory,
+ Oid *outfuncoid);
+extern Datum to_jsonb_worker(Datum val, JsonbTypeCategory tcategory,
+ Oid outfuncoid);
+extern Datum jsonb_build_object_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null,
+ bool unique_keys);
+extern Datum jsonb_build_array_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null);
+
#endif /* __JSONB_H__ */
diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h
index 0ca48591d0..9e9128e8b2 100644
--- a/src/include/utils/jsonfuncs.h
+++ b/src/include/utils/jsonfuncs.h
@@ -45,6 +45,9 @@ extern void pg_parse_json_or_ereport(JsonLexContext *lex, JsonSemAction *sem);
/* report an error during json lexing or parsing */
extern void json_ereport_error(JsonParseErrorType error, JsonLexContext *lex);
+/* get first JSON token */
+extern JsonTokenType json_get_first_token(text *json, bool throw_error);
+
extern uint32 parse_jsonb_index_flags(Jsonb *jb);
extern void iterate_jsonb_values(Jsonb *jb, uint32 flags, void *state,
JsonIterateStringValuesAction action);
@@ -55,4 +58,8 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
extern text *transform_json_string_values(text *json, void *action_state,
JsonTransformStringValuesAction transform_action);
+extern Datum json_populate_type(Datum json_val, Oid json_type,
+ Oid typid, int32 typmod,
+ void **cache, MemoryContext mcxt, bool *isnull);
+
#endif
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 87d302b702..fd2663c22f 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -16,7 +16,9 @@
#include "fmgr.h"
#include "nodes/pg_list.h"
+#include "nodes/primnodes.h"
#include "utils/jsonb.h"
+#include "utils/jsonfuncs.h"
typedef struct
{
@@ -174,6 +176,7 @@ extern bool jspGetBool(JsonPathItem *v);
extern char *jspGetString(JsonPathItem *v, int32 *len);
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
JsonPathItem *to, int i);
+extern bool jspIsMutable(JsonPath *path, List *varnames, List *varexprs);
extern const char *jspOperationName(JsonPathItemType type);
@@ -248,4 +251,34 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len);
extern int jspConvertRegexFlags(uint32 xflags);
+/*
+ * Evaluation of jsonpath
+ */
+
+/* External variable passed into jsonpath. */
+typedef struct JsonPathVariableEvalContext
+{
+ char *name;
+ Oid typid;
+ int32 typmod;
+ struct ExprContext *econtext;
+ struct ExprState *estate;
+ Datum value;
+ bool isnull;
+ bool evaluated;
+} JsonPathVariableEvalContext;
+
+/* SQL/JSON item */
+extern void JsonItemFromDatum(Datum val, Oid typid, int32 typmod,
+ JsonbValue *res);
+
+extern bool JsonPathExists(Datum jb, JsonPath *path, List *vars, bool *error);
+extern Datum JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper,
+ bool *empty, bool *error, List *vars);
+extern JsonbValue *JsonPathValue(Datum jb, JsonPath *jp, bool *empty,
+ bool *error, List *vars);
+
+extern int EvalJsonPathVar(void *vars, char *varName, int varNameLen,
+ JsonbValue *val, JsonbValue *baseObject);
+
#endif
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index 1e24801a6f..5a8c2f3eef 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -47,6 +47,8 @@ my %replace_string = (
'NOT_LA' => 'not',
'NULLS_LA' => 'nulls',
'WITH_LA' => 'with',
+ 'WITH_LA_UNIQUE' => 'with',
+ 'WITHOUT_LA' => 'without',
'TYPECAST' => '::',
'DOT_DOT' => '..',
'COLON_EQUALS' => ':=',
diff --git a/src/interfaces/ecpg/preproc/parser.c b/src/interfaces/ecpg/preproc/parser.c
index a8571a3ffa..c47dee05cb 100644
--- a/src/interfaces/ecpg/preproc/parser.c
+++ b/src/interfaces/ecpg/preproc/parser.c
@@ -83,6 +83,7 @@ filtered_base_yylex(void)
case WITH:
case UIDENT:
case USCONST:
+ case WITHOUT:
break;
default:
return cur_token;
@@ -143,6 +144,19 @@ filtered_base_yylex(void)
case ORDINALITY:
cur_token = WITH_LA;
break;
+ case UNIQUE:
+ cur_token = WITH_LA_UNIQUE;
+ break;
+ }
+ break;
+
+ case WITHOUT:
+ /* Replace WITHOUT by WITHOUT_LA if it's followed by TIME */
+ switch (next_token)
+ {
+ case TIME:
+ cur_token = WITHOUT_LA;
+ break;
}
break;
case UIDENT:
diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regress/expected/json_sqljson.out
new file mode 100644
index 0000000000..bb62634314
--- /dev/null
+++ b/src/test/regress/expected/json_sqljson.out
@@ -0,0 +1,15 @@
+-- JSON_EXISTS
+SELECT JSON_EXISTS(NULL FORMAT JSON, '$');
+ERROR: JSON_EXISTS() is not yet implemented for json type
+LINE 1: SELECT JSON_EXISTS(NULL FORMAT JSON, '$');
+ ^
+-- JSON_VALUE
+SELECT JSON_VALUE(NULL FORMAT JSON, '$');
+ERROR: JSON_VALUE() is not yet implemented for json type
+LINE 1: SELECT JSON_VALUE(NULL FORMAT JSON, '$');
+ ^
+-- JSON_QUERY
+SELECT JSON_QUERY(NULL FORMAT JSON, '$');
+ERROR: JSON_QUERY() is not yet implemented for json type
+LINE 1: SELECT JSON_QUERY(NULL FORMAT JSON, '$');
+ ^
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index a9cd145aec..905b50e850 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5509,3 +5509,133 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
12345
(1 row)
+-- test mapping of jsonb to SQL/JSON JSON type
+select json(' { "aa": 1, "b" : 2 }');
+ json
+-----------------------
+ { "aa": 1, "b" : 2 }
+(1 row)
+
+select json ' { "aa": 1, "b" : 2 }';
+ json
+-----------------------
+ { "aa": 1, "b" : 2 }
+(1 row)
+
+select pg_catalog.json ' { "aa": 1, "b" : 2 }';
+ json
+-----------------------
+ { "aa": 1, "b" : 2 }
+(1 row)
+
+create table test_json_as_json (js json, jb jsonb);
+\d test_json_as_json
+ Table "public.test_json_as_json"
+ Column | Type | Collation | Nullable | Default
+--------+-------+-----------+----------+---------
+ js | json | | |
+ jb | jsonb | | |
+
+set sql_json = jsonb;
+select json(' { "aa": 1, "b" : 2 }');
+ json
+-------------------
+ {"b": 2, "aa": 1}
+(1 row)
+
+select json ' { "aa": 1, "b" : 2 }';
+ jsonb
+-------------------
+ {"b": 2, "aa": 1}
+(1 row)
+
+select pg_catalog.json ' { "aa": 1, "b" : 2 }';
+ json
+-----------------------
+ { "aa": 1, "b" : 2 }
+(1 row)
+
+\d test_json_as_json
+ Table "public.test_json_as_json"
+ Column | Type | Collation | Nullable | Default
+--------+-----------------+-----------+----------+---------
+ js | pg_catalog.json | | |
+ jb | json | | |
+
+create table test_json_as_jsonb (js json, jb jsonb, jt pg_catalog.json);
+\d test_json_as_jsonb
+ Table "public.test_json_as_jsonb"
+ Column | Type | Collation | Nullable | Default
+--------+-----------------+-----------+----------+---------
+ js | json | | |
+ jb | json | | |
+ jt | pg_catalog.json | | |
+
+insert into test_json_as_jsonb values ('{ "a" : 1 }', '{ "a" : 1 }', '{ "a" : 1 }');
+select * from test_json_as_jsonb;
+ js | jb | jt
+----------+----------+-------------
+ {"a": 1} | {"a": 1} | { "a" : 1 }
+(1 row)
+
+select jsonb_object_field(js, 'a') from test_json_as_jsonb;
+ jsonb_object_field
+--------------------
+ 1
+(1 row)
+
+select jsonb_object_field(jb, 'a') from test_json_as_jsonb;
+ jsonb_object_field
+--------------------
+ 1
+(1 row)
+
+select jsonb_object_field(jt, 'a') from test_json_as_jsonb;
+ERROR: function jsonb_object_field(pg_catalog.json, unknown) does not exist
+LINE 1: select jsonb_object_field(jt, 'a') from test_json_as_jsonb;
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select json_object_field(jt, 'a') from test_json_as_jsonb;
+ json_object_field
+-------------------
+ 1
+(1 row)
+
+set sql_json = json;
+\d test_json_as_jsonb
+ Table "public.test_json_as_jsonb"
+ Column | Type | Collation | Nullable | Default
+--------+-------+-----------+----------+---------
+ js | jsonb | | |
+ jb | jsonb | | |
+ jt | json | | |
+
+select * from test_json_as_jsonb;
+ js | jb | jt
+----------+----------+-------------
+ {"a": 1} | {"a": 1} | { "a" : 1 }
+(1 row)
+
+select jsonb_object_field(js, 'a') from test_json_as_jsonb;
+ jsonb_object_field
+--------------------
+ 1
+(1 row)
+
+select jsonb_object_field(jb, 'a') from test_json_as_jsonb;
+ jsonb_object_field
+--------------------
+ 1
+(1 row)
+
+select jsonb_object_field(jt, 'a') from test_json_as_jsonb;
+ERROR: function jsonb_object_field(json, unknown) does not exist
+LINE 1: select jsonb_object_field(jt, 'a') from test_json_as_jsonb;
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select json_object_field(jt, 'a') from test_json_as_jsonb;
+ json_object_field
+-------------------
+ 1
+(1 row)
+
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
new file mode 100644
index 0000000000..f2f5e271b8
--- /dev/null
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -0,0 +1,1018 @@
+-- JSON_EXISTS
+SELECT JSON_EXISTS(NULL::jsonb, '$');
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '[]', '$');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(JSON_OBJECT(RETURNING jsonb), '$');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '1', '$');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(jsonb 'null', '$');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '[]', '$');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '1', '$.a');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '1', 'strict $.a');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '1', 'strict $.a' ERROR ON ERROR);
+ERROR: jsonpath member accessor can only be applied to an object
+SELECT JSON_EXISTS(jsonb 'null', '$.a');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '[]', '$.a');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '[1, "aaa", {"a": 1}]', 'strict $.a');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '[1, "aaa", {"a": 1}]', 'lax $.a');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '{}', '$.a');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '{"b": 1, "a": 2}', '$.a');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '1', '$.a.b');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '{"a": {"b": 1}}', '$.a.b');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.a.b');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING 1 AS x);
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING '1' AS x);
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 2 AS y);
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 1 AS y);
+ json_exists
+-------------
+ f
+(1 row)
+
+-- extension: boolean expressions
+SELECT JSON_EXISTS(jsonb '1', '$ > 2');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '1', '$.a > 2' ERROR ON ERROR);
+ json_exists
+-------------
+ t
+(1 row)
+
+-- extension: RETURNING clause
+SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING bool);
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING bool);
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING int);
+ json_exists
+-------------
+ 1
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING int);
+ json_exists
+-------------
+ 0
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING text);
+ json_exists
+-------------
+ true
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING text);
+ json_exists
+-------------
+ false
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '1', 'strict $[1]' RETURNING text FALSE ON ERROR);
+ json_exists
+-------------
+ false
+(1 row)
+
+SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb);
+ERROR: cannot cast type boolean to jsonb
+LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb);
+ ^
+SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4);
+ERROR: cannot cast type boolean to real
+LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4);
+ ^
+-- JSON_VALUE
+SELECT JSON_VALUE(NULL::jsonb, '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(jsonb 'null', '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING int);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(jsonb 'true', '$');
+ json_value
+------------
+ true
+(1 row)
+
+SELECT JSON_VALUE(jsonb 'true', '$' RETURNING bool);
+ json_value
+------------
+ t
+(1 row)
+
+SELECT JSON_VALUE(jsonb '123', '$');
+ json_value
+------------
+ 123
+(1 row)
+
+SELECT JSON_VALUE(jsonb '123', '$' RETURNING int) + 234;
+ ?column?
+----------
+ 357
+(1 row)
+
+SELECT JSON_VALUE(jsonb '123', '$' RETURNING text);
+ json_value
+------------
+ 123
+(1 row)
+
+/* jsonb bytea ??? */
+SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR);
+ERROR: SQL/JSON item cannot be cast to target type
+SELECT JSON_VALUE(jsonb '1.23', '$');
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING int);
+ json_value
+------------
+ 1
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING int ERROR ON ERROR);
+ERROR: invalid input syntax for type integer: "1.23"
+SELECT JSON_VALUE(jsonb '"aaa"', '$');
+ json_value
+------------
+ aaa
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING text);
+ json_value
+------------
+ aaa
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(5));
+ json_value
+------------
+ aaa
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(2));
+ json_value
+------------
+ aa
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json);
+ json_value
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb);
+ json_value
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json ERROR ON ERROR);
+ json_value
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb ERROR ON ERROR);
+ json_value
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING json);
+ json_value
+------------
+ "\"aaa\""
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING jsonb);
+ json_value
+------------
+ "\"aaa\""
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int ERROR ON ERROR);
+ERROR: invalid input syntax for type integer: "aaa"
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR);
+ json_value
+------------
+ 111
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234;
+ ?column?
+----------
+ 357
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9;
+ ?column?
+------------
+ 03-01-2017
+(1 row)
+
+-- Test NULL checks execution in domain types
+CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL;
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null);
+ERROR: domain sqljsonb_int_not_null does not allow null values
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null NULL ON ERROR);
+ERROR: domain sqljsonb_int_not_null does not allow null values
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR);
+ERROR: domain sqljsonb_int_not_null does not allow null values
+SELECT JSON_VALUE(jsonb '[]', '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR);
+ERROR: JSON path expression in JSON_VALUE should return singleton scalar item
+SELECT JSON_VALUE(jsonb '{}', '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(jsonb '{}', '$' ERROR ON ERROR);
+ERROR: JSON path expression in JSON_VALUE should return singleton scalar item
+SELECT JSON_VALUE(jsonb '1', '$.a');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(jsonb '1', 'strict $.a' ERROR ON ERROR);
+ERROR: jsonpath member accessor can only be applied to an object
+SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 'error' ON ERROR);
+ json_value
+------------
+ error
+(1 row)
+
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON ERROR);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY ERROR ON ERROR);
+ERROR: no SQL/JSON item
+SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 2 ON ERROR);
+ json_value
+------------
+ 2
+(1 row)
+
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT 2 ON ERROR);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON ERROR);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR);
+ json_value
+------------
+ 2
+(1 row)
+
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR);
+ json_value
+------------
+ 3
+(1 row)
+
+SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' ERROR ON ERROR);
+ERROR: JSON path expression in JSON_VALUE should return singleton scalar item
+SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' DEFAULT '0' ON ERROR);
+ json_value
+------------
+ 0
+(1 row)
+
+SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int ERROR ON ERROR);
+ERROR: invalid input syntax for type integer: " "
+SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR);
+ json_value
+------------
+ 5
+(1 row)
+
+SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR);
+ json_value
+------------
+ 1
+(1 row)
+
+SELECT
+ x,
+ JSON_VALUE(
+ jsonb '{"a": 1, "b": 2}',
+ '$.* ? (@ > $x)' PASSING x AS x
+ RETURNING int
+ DEFAULT -1 ON EMPTY
+ DEFAULT -2 ON ERROR
+ ) y
+FROM
+ generate_series(0, 2) x;
+ x | y
+---+----
+ 0 | -2
+ 1 | 2
+ 2 | -1
+(3 rows)
+
+SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a);
+ json_value
+------------
+ (1,2)
+(1 row)
+
+SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point);
+ json_value
+------------
+ (1,2)
+(1 row)
+
+-- Test timestamptz passing and output
+SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
+ json_value
+------------------------------
+ Tue Feb 20 18:34:56 2018 PST
+(1 row)
+
+SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamptz);
+ json_value
+------------------------------
+ Tue Feb 20 18:34:56 2018 PST
+(1 row)
+
+SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamp);
+ json_value
+--------------------------
+ Tue Feb 20 18:34:56 2018
+(1 row)
+
+SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json);
+ json_value
+-----------------------------
+ "2018-02-21T02:34:56+00:00"
+(1 row)
+
+SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb);
+ json_value
+-----------------------------
+ "2018-02-21T02:34:56+00:00"
+(1 row)
+
+-- JSON_QUERY
+SELECT
+ JSON_QUERY(js, '$'),
+ JSON_QUERY(js, '$' WITHOUT WRAPPER),
+ JSON_QUERY(js, '$' WITH CONDITIONAL WRAPPER),
+ JSON_QUERY(js, '$' WITH UNCONDITIONAL ARRAY WRAPPER),
+ JSON_QUERY(js, '$' WITH ARRAY WRAPPER)
+FROM
+ (VALUES
+ (jsonb 'null'),
+ ('12.3'),
+ ('true'),
+ ('"aaa"'),
+ ('[1, null, "2"]'),
+ ('{"a": 1, "b": [2]}')
+ ) foo(js);
+ json_query | json_query | json_query | json_query | json_query
+--------------------+--------------------+--------------------+----------------------+----------------------
+ null | null | [null] | [null] | [null]
+ 12.3 | 12.3 | [12.3] | [12.3] | [12.3]
+ true | true | [true] | [true] | [true]
+ "aaa" | "aaa" | ["aaa"] | ["aaa"] | ["aaa"]
+ [1, null, "2"] | [1, null, "2"] | [1, null, "2"] | [[1, null, "2"]] | [[1, null, "2"]]
+ {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | [{"a": 1, "b": [2]}] | [{"a": 1, "b": [2]}]
+(6 rows)
+
+SELECT
+ JSON_QUERY(js, 'strict $[*]') AS "unspec",
+ JSON_QUERY(js, 'strict $[*]' WITHOUT WRAPPER) AS "without",
+ JSON_QUERY(js, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond",
+ JSON_QUERY(js, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond",
+ JSON_QUERY(js, 'strict $[*]' WITH ARRAY WRAPPER) AS "with"
+FROM
+ (VALUES
+ (jsonb '1'),
+ ('[]'),
+ ('[null]'),
+ ('[12.3]'),
+ ('[true]'),
+ ('["aaa"]'),
+ ('[[1, 2, 3]]'),
+ ('[{"a": 1, "b": [2]}]'),
+ ('[1, "2", null, [3]]')
+ ) foo(js);
+ unspec | without | with cond | with uncond | with
+--------------------+--------------------+---------------------+----------------------+----------------------
+ | | | |
+ | | | |
+ null | null | [null] | [null] | [null]
+ 12.3 | 12.3 | [12.3] | [12.3] | [12.3]
+ true | true | [true] | [true] | [true]
+ "aaa" | "aaa" | ["aaa"] | ["aaa"] | ["aaa"]
+ [1, 2, 3] | [1, 2, 3] | [1, 2, 3] | [[1, 2, 3]] | [[1, 2, 3]]
+ {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | [{"a": 1, "b": [2]}] | [{"a": 1, "b": [2]}]
+ | | [1, "2", null, [3]] | [1, "2", null, [3]] | [1, "2", null, [3]]
+(9 rows)
+
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text);
+ json_query
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES);
+ json_query
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES ON SCALAR STRING);
+ json_query
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES);
+ json_query
+------------
+ aaa
+(1 row)
+
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING);
+ json_query
+------------
+ aaa
+(1 row)
+
+SELECT JSON_QUERY(jsonb '"aaa"', '$' OMIT QUOTES ERROR ON ERROR);
+ERROR: invalid input syntax for type json
+DETAIL: Token "aaa" is invalid.
+CONTEXT: JSON data, line 1: aaa
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES ERROR ON ERROR);
+ERROR: invalid input syntax for type json
+DETAIL: Token "aaa" is invalid.
+CONTEXT: JSON data, line 1: aaa
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR);
+ json_query
+------------
+ \x616161
+(1 row)
+
+-- QUOTES behavior should not be specified when WITH WRAPPER used:
+-- Should fail
+SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES);
+ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used
+LINE 1: SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES)...
+ ^
+SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER KEEP QUOTES);
+ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used
+LINE 1: SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER KEEP QUOTES)...
+ ^
+SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTES);
+ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used
+LINE 1: ...N_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTE...
+ ^
+SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES);
+ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used
+LINE 1: ...N_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTE...
+ ^
+-- Should succeed
+SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER OMIT QUOTES);
+ json_query
+------------
+ [1]
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER KEEP QUOTES);
+ json_query
+------------
+ [1]
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[]', '$[*]');
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[]', '$[*]' NULL ON EMPTY);
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ON EMPTY);
+ json_query
+------------
+ []
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ARRAY ON EMPTY);
+ json_query
+------------
+ []
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY OBJECT ON EMPTY);
+ json_query
+------------
+ {}
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY);
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[]', '$[*]' DEFAULT '"empty"' ON EMPTY);
+ json_query
+------------
+ "empty"
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY NULL ON ERROR);
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR);
+ json_query
+------------
+ []
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR);
+ json_query
+------------
+ {}
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY ERROR ON ERROR);
+ERROR: no SQL/JSON item
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON ERROR);
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' ERROR ON ERROR);
+ERROR: JSON path expression in JSON_QUERY should return singleton item without wrapper
+HINT: use WITH WRAPPER clause to wrap SQL/JSON item sequence into array
+SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' DEFAULT '"empty"' ON ERROR);
+ json_query
+------------
+ "empty"
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json);
+ json_query
+------------
+ [1, 2]
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json FORMAT JSON);
+ json_query
+------------
+ [1, 2]
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING jsonb);
+ json_query
+------------
+ [1, 2]
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING jsonb FORMAT JSON);
+ json_query
+------------
+ [1, 2]
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text);
+ json_query
+------------
+ [1, 2]
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING char(10));
+ json_query
+------------
+ [1, 2]
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING char(3));
+ json_query
+------------
+ [1,
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text FORMAT JSON);
+ json_query
+------------
+ [1, 2]
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea);
+ json_query
+----------------
+ \x5b312c20325d
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea FORMAT JSON);
+ json_query
+----------------
+ \x5b312c20325d
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR);
+ json_query
+------------
+ \x7b7d
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT ON ERROR);
+ json_query
+------------
+ \x7b7d
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR);
+ json_query
+------------
+ {}
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING jsonb EMPTY OBJECT ON ERROR);
+ json_query
+------------
+ {}
+(1 row)
+
+SELECT
+ x, y,
+ JSON_QUERY(
+ jsonb '[1,2,3,4,5,null]',
+ '$[*] ? (@ >= $x && @ <= $y)'
+ PASSING x AS x, y AS y
+ WITH CONDITIONAL WRAPPER
+ EMPTY ARRAY ON EMPTY
+ ) list
+FROM
+ generate_series(0, 4) x,
+ generate_series(0, 4) y;
+ x | y | list
+---+---+--------------
+ 0 | 0 | []
+ 0 | 1 | [1]
+ 0 | 2 | [1, 2]
+ 0 | 3 | [1, 2, 3]
+ 0 | 4 | [1, 2, 3, 4]
+ 1 | 0 | []
+ 1 | 1 | [1]
+ 1 | 2 | [1, 2]
+ 1 | 3 | [1, 2, 3]
+ 1 | 4 | [1, 2, 3, 4]
+ 2 | 0 | []
+ 2 | 1 | []
+ 2 | 2 | [2]
+ 2 | 3 | [2, 3]
+ 2 | 4 | [2, 3, 4]
+ 3 | 0 | []
+ 3 | 1 | []
+ 3 | 2 | []
+ 3 | 3 | [3]
+ 3 | 4 | [3, 4]
+ 4 | 0 | []
+ 4 | 1 | []
+ 4 | 2 | []
+ 4 | 3 | []
+ 4 | 4 | [4]
+(25 rows)
+
+-- Extension: record types returning
+CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]);
+CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]);
+SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec);
+ json_query
+-----------------------------------------------------
+ (1,aaa,"[1, ""2"", {}]","{""x"": [1, ""2"", {}]}",)
+(1 row)
+
+SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa);
+ unnest
+------------------------
+ {"a": 1, "b": ["foo"]}
+ {"a": 2, "c": {}}
+ 123
+(3 rows)
+
+SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca);
+ a | t | js | jb | jsa
+---+-------------+----+------------+-----
+ 1 | ["foo", []] | | |
+ 2 | | | [{}, true] |
+(2 rows)
+
+-- Extension: array types returning
+SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER);
+ json_query
+--------------
+ {1,2,NULL,3}
+(1 row)
+
+SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[]));
+ a | t | js | jb | jsa
+---+-------------+----+------------+-----
+ 1 | ["foo", []] | | |
+ 2 | | | [{}, true] |
+(2 rows)
+
+-- Extension: domain types returning
+SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null);
+ json_query
+------------
+ 1
+(1 row)
+
+SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null);
+ERROR: domain sqljsonb_int_not_null does not allow null values
+-- Test timestamptz passing and output
+SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
+ json_query
+-----------------------------
+ "2018-02-21T02:34:56+00:00"
+(1 row)
+
+SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json);
+ json_query
+-----------------------------
+ "2018-02-21T02:34:56+00:00"
+(1 row)
+
+SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb);
+ json_query
+-----------------------------
+ "2018-02-21T02:34:56+00:00"
+(1 row)
+
+-- Test constraints
+CREATE TABLE test_jsonb_constraints (
+ js text,
+ i int,
+ x jsonb DEFAULT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH WRAPPER)
+ CONSTRAINT test_jsonb_constraint1
+ CHECK (js IS JSON)
+ CONSTRAINT test_jsonb_constraint2
+ CHECK (JSON_EXISTS(js::jsonb, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr))
+ CONSTRAINT test_jsonb_constraint3
+ CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i)
+ CONSTRAINT test_jsonb_constraint4
+ CHECK (JSON_QUERY(js::jsonb, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]')
+ CONSTRAINT test_jsonb_constraint5
+ CHECK (JSON_QUERY(js::jsonb, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a' COLLATE "C")
+ CONSTRAINT test_jsonb_constraint6
+ CHECK (JSON_EXISTS(js::jsonb, 'strict $.a' RETURNING int TRUE ON ERROR) < 2)
+);
+\d test_jsonb_constraints
+ Table "public.test_jsonb_constraints"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+--------------------------------------------------------------------------------
+ js | text | | |
+ i | integer | | |
+ x | jsonb | | | JSON_QUERY('[1, 2]'::jsonb, '$[*]' RETURNING jsonb WITH UNCONDITIONAL WRAPPER)
+Check constraints:
+ "test_jsonb_constraint1" CHECK (js IS JSON)
+ "test_jsonb_constraint2" CHECK (JSON_EXISTS(js::jsonb, '$."a"' PASSING i + 5 AS int, i::text AS txt, ARRAY[1, 2, 3] AS arr))
+ "test_jsonb_constraint3" CHECK (JSON_VALUE(js::jsonb, '$."a"' RETURNING integer DEFAULT ('12'::text || i)::integer ON EMPTY ERROR ON ERROR) > i)
+ "test_jsonb_constraint4" CHECK (JSON_QUERY(js::jsonb, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < '[10]'::jsonb)
+ "test_jsonb_constraint5" CHECK (JSON_QUERY(js::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > ('a'::bpchar COLLATE "C"))
+ "test_jsonb_constraint6" CHECK (JSON_EXISTS(js::jsonb, 'strict $."a"' RETURNING integer TRUE ON ERROR) < 2)
+
+SELECT check_clause
+FROM information_schema.check_constraints
+WHERE constraint_name LIKE 'test_jsonb_constraint%';
+ check_clause
+--------------------------------------------------------------------------------------------------------------------------
+ ((js IS JSON))
+ (JSON_EXISTS((js)::jsonb, '$."a"' PASSING (i + 5) AS int, (i)::text AS txt, ARRAY[1, 2, 3] AS arr))
+ ((JSON_VALUE((js)::jsonb, '$."a"' RETURNING integer DEFAULT (('12'::text || i))::integer ON EMPTY ERROR ON ERROR) > i))
+ ((JSON_QUERY((js)::jsonb, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < '[10]'::jsonb))
+ ((JSON_QUERY((js)::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > ('a'::bpchar COLLATE "C")))
+ ((JSON_EXISTS((js)::jsonb, 'strict $."a"' RETURNING integer TRUE ON ERROR) < 2))
+(6 rows)
+
+SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'test_jsonb_constraints'::regclass;
+ pg_get_expr
+--------------------------------------------------------------------------------
+ JSON_QUERY('[1, 2]'::jsonb, '$[*]' RETURNING jsonb WITH UNCONDITIONAL WRAPPER)
+(1 row)
+
+INSERT INTO test_jsonb_constraints VALUES ('', 1);
+ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint1"
+DETAIL: Failing row contains (, 1, [1, 2]).
+INSERT INTO test_jsonb_constraints VALUES ('1', 1);
+ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint2"
+DETAIL: Failing row contains (1, 1, [1, 2]).
+INSERT INTO test_jsonb_constraints VALUES ('[]');
+ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint2"
+DETAIL: Failing row contains ([], null, [1, 2]).
+INSERT INTO test_jsonb_constraints VALUES ('{"b": 1}', 1);
+ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint2"
+DETAIL: Failing row contains ({"b": 1}, 1, [1, 2]).
+INSERT INTO test_jsonb_constraints VALUES ('{"a": 1}', 1);
+ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint3"
+DETAIL: Failing row contains ({"a": 1}, 1, [1, 2]).
+INSERT INTO test_jsonb_constraints VALUES ('{"a": 7}', 1);
+ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint5"
+DETAIL: Failing row contains ({"a": 7}, 1, [1, 2]).
+INSERT INTO test_jsonb_constraints VALUES ('{"a": 10}', 1);
+ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint4"
+DETAIL: Failing row contains ({"a": 10}, 1, [1, 2]).
+DROP TABLE test_jsonb_constraints;
+-- Test mutabilily od query functions
+CREATE TABLE test_jsonb_mutability(js jsonb);
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a[0]'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime()'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@ < $.datetime())'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime())'));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime("HH:MI TZH"))'));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("HH:MI TZH"))'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI") < $.datetime("YY-MM-DD HH:MI"))'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("YY-MM-DD HH:MI"))'));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $x' PASSING '12:34'::timetz AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $y' PASSING '12:34'::timetz AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '12:34'::timetz AS x));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '1234'::int AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() ? (@ == $x)' PASSING '12:34'::time AS x));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("YY-MM-DD") ? (@ == $x)' PASSING '2020-07-14'::date AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x));
+DROP TABLE test_jsonb_mutability;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 562b586d8e..e7d6358a25 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1473,8 +1473,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
index 0000000000..51bd216120
--- /dev/null
+++ b/src/test/regress/expected/sqljson.out
@@ -0,0 +1,1352 @@
+-- JSON()
+SELECT JSON();
+ERROR: syntax error at or near ")"
+LINE 1: SELECT JSON();
+ ^
+SELECT JSON(NULL);
+ json
+------
+
+(1 row)
+
+SELECT JSON('{ "a" : 1 } ');
+ json
+--------------
+ { "a" : 1 }
+(1 row)
+
+SELECT JSON('{ "a" : 1 } ' FORMAT JSON);
+ json
+--------------
+ { "a" : 1 }
+(1 row)
+
+SELECT JSON('{ "a" : 1 } ' FORMAT JSON ENCODING UTF8);
+ERROR: JSON ENCODING clause is only allowed for bytea input type
+LINE 1: SELECT JSON('{ "a" : 1 } ' FORMAT JSON ENCODING UTF8);
+ ^
+SELECT JSON('{ "a" : 1 } '::bytea FORMAT JSON ENCODING UTF8);
+ json
+--------------
+ { "a" : 1 }
+(1 row)
+
+SELECT pg_typeof(JSON('{ "a" : 1 } '));
+ pg_typeof
+-----------
+ json
+(1 row)
+
+SELECT JSON(' 1 '::json);
+ json
+---------
+ 1
+(1 row)
+
+SELECT JSON(' 1 '::jsonb);
+ json
+------
+ 1
+(1 row)
+
+SELECT JSON(' 1 '::json WITH UNIQUE KEYS);
+ERROR: cannot use non-string types with WITH UNIQUE KEYS clause
+LINE 1: SELECT JSON(' 1 '::json WITH UNIQUE KEYS);
+ ^
+SELECT JSON(123);
+ERROR: cannot cast type integer to json
+LINE 1: SELECT JSON(123);
+ ^
+SELECT JSON('{"a": 1, "a": 2}');
+ json
+------------------
+ {"a": 1, "a": 2}
+(1 row)
+
+SELECT JSON('{"a": 1, "a": 2}' WITH UNIQUE KEYS);
+ERROR: duplicate JSON object key value
+SELECT JSON('{"a": 1, "a": 2}' WITHOUT UNIQUE KEYS);
+ json
+------------------
+ {"a": 1, "a": 2}
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123');
+ QUERY PLAN
+-----------------------------
+ Result
+ Output: JSON('123'::json)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' FORMAT JSON);
+ QUERY PLAN
+-----------------------------
+ Result
+ Output: JSON('123'::json)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON);
+ QUERY PLAN
+-----------------------------------------------
+ Result
+ Output: JSON('\x313233'::bytea FORMAT JSON)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON ENCODING UTF8);
+ QUERY PLAN
+-------------------------------------------------------------
+ Result
+ Output: JSON('\x313233'::bytea FORMAT JSON ENCODING UTF8)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITH UNIQUE KEYS);
+ QUERY PLAN
+----------------------------------------------
+ Result
+ Output: JSON('123'::text WITH UNIQUE KEYS)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITHOUT UNIQUE KEYS);
+ QUERY PLAN
+-----------------------------
+ Result
+ Output: JSON('123'::json)
+(2 rows)
+
+SELECT JSON('123' RETURNING text);
+ERROR: cannot use RETURNING type text in JSON()
+LINE 1: SELECT JSON('123' RETURNING text);
+ ^
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123');
+ QUERY PLAN
+-----------------------------
+ Result
+ Output: JSON('123'::json)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json);
+ QUERY PLAN
+-----------------------------
+ Result
+ Output: JSON('123'::json)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb);
+ QUERY PLAN
+----------------------------------------------
+ Result
+ Output: JSON('123'::jsonb RETURNING jsonb)
+(2 rows)
+
+SELECT pg_typeof(JSON('123'));
+ pg_typeof
+-----------
+ json
+(1 row)
+
+SELECT pg_typeof(JSON('123' RETURNING json));
+ pg_typeof
+-----------
+ json
+(1 row)
+
+SELECT pg_typeof(JSON('123' RETURNING jsonb));
+ pg_typeof
+-----------
+ jsonb
+(1 row)
+
+SET sql_json = jsonb;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123');
+ QUERY PLAN
+-----------------------------
+ Result
+ Output: JSON('123'::json)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json);
+ QUERY PLAN
+-----------------------------
+ Result
+ Output: JSON('123'::json)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb);
+ QUERY PLAN
+-----------------------------
+ Result
+ Output: JSON('123'::json)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING pg_catalog.json);
+ QUERY PLAN
+------------------------------------------------------------------
+ Result
+ Output: JSON('123'::pg_catalog.json RETURNING pg_catalog.json)
+(2 rows)
+
+SELECT pg_typeof(JSON('123'));
+ pg_typeof
+-----------
+ json
+(1 row)
+
+SELECT pg_typeof(JSON('123' RETURNING json));
+ pg_typeof
+-----------
+ json
+(1 row)
+
+SELECT pg_typeof(JSON('123' RETURNING jsonb));
+ pg_typeof
+-----------
+ json
+(1 row)
+
+SELECT pg_typeof(JSON('123' RETURNING pg_catalog.json));
+ pg_typeof
+-----------------
+ pg_catalog.json
+(1 row)
+
+SET sql_json = json;
+-- JSON_SCALAR()
+SELECT JSON_SCALAR();
+ERROR: syntax error at or near ")"
+LINE 1: SELECT JSON_SCALAR();
+ ^
+SELECT JSON_SCALAR(NULL);
+ json_scalar
+-------------
+
+(1 row)
+
+SELECT JSON_SCALAR(NULL::int);
+ json_scalar
+-------------
+
+(1 row)
+
+SELECT JSON_SCALAR(123);
+ json_scalar
+-------------
+ 123
+(1 row)
+
+SELECT JSON_SCALAR(123.45);
+ json_scalar
+-------------
+ 123.45
+(1 row)
+
+SELECT JSON_SCALAR(123.45::numeric);
+ json_scalar
+-------------
+ 123.45
+(1 row)
+
+SELECT JSON_SCALAR(true);
+ json_scalar
+-------------
+ true
+(1 row)
+
+SELECT JSON_SCALAR(false);
+ json_scalar
+-------------
+ false
+(1 row)
+
+SELECT JSON_SCALAR(' 123.45');
+ json_scalar
+-------------
+ " 123.45"
+(1 row)
+
+SELECT JSON_SCALAR('2020-06-07'::date);
+ json_scalar
+--------------
+ "2020-06-07"
+(1 row)
+
+SELECT JSON_SCALAR('2020-06-07 01:02:03'::timestamp);
+ json_scalar
+-----------------------
+ "2020-06-07T01:02:03"
+(1 row)
+
+SELECT JSON_SCALAR('{}'::json);
+ json_scalar
+-------------
+ {}
+(1 row)
+
+SELECT JSON_SCALAR('{}'::jsonb);
+ json_scalar
+-------------
+ {}
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123);
+ QUERY PLAN
+----------------------------
+ Result
+ Output: JSON_SCALAR(123)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123');
+ QUERY PLAN
+------------------------------------
+ Result
+ Output: JSON_SCALAR('123'::text)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json);
+ QUERY PLAN
+----------------------------
+ Result
+ Output: JSON_SCALAR(123)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb);
+ QUERY PLAN
+--------------------------------------------
+ Result
+ Output: JSON_SCALAR(123 RETURNING jsonb)
+(2 rows)
+
+SET sql_json = jsonb;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123);
+ QUERY PLAN
+----------------------------
+ Result
+ Output: JSON_SCALAR(123)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json);
+ QUERY PLAN
+----------------------------
+ Result
+ Output: JSON_SCALAR(123)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb);
+ QUERY PLAN
+----------------------------
+ Result
+ Output: JSON_SCALAR(123)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING pg_catalog.json);
+ QUERY PLAN
+------------------------------------------------------
+ Result
+ Output: JSON_SCALAR(123 RETURNING pg_catalog.json)
+(2 rows)
+
+SET sql_json = json;
+-- JSON_SERIALIZE()
+SELECT JSON_SERIALIZE();
+ERROR: syntax error at or near ")"
+LINE 1: SELECT JSON_SERIALIZE();
+ ^
+SELECT JSON_SERIALIZE(NULL);
+ json_serialize
+----------------
+
+(1 row)
+
+SELECT JSON_SERIALIZE(JSON('{ "a" : 1 } '));
+ json_serialize
+----------------
+ { "a" : 1 }
+(1 row)
+
+SELECT JSON_SERIALIZE('{ "a" : 1 } ');
+ json_serialize
+----------------
+ { "a" : 1 }
+(1 row)
+
+SELECT JSON_SERIALIZE('1');
+ json_serialize
+----------------
+ 1
+(1 row)
+
+SELECT JSON_SERIALIZE('1' FORMAT JSON);
+ json_serialize
+----------------
+ 1
+(1 row)
+
+SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING bytea);
+ json_serialize
+----------------------------
+ \x7b20226122203a2031207d20
+(1 row)
+
+SELECT pg_typeof(JSON_SERIALIZE(NULL));
+ pg_typeof
+-----------
+ text
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SERIALIZE('{}');
+ QUERY PLAN
+-----------------------------------------------------
+ Result
+ Output: JSON_SERIALIZE('{}'::json RETURNING text)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SERIALIZE('{}' RETURNING bytea);
+ QUERY PLAN
+------------------------------------------------------
+ Result
+ Output: JSON_SERIALIZE('{}'::json RETURNING bytea)
+(2 rows)
+
+-- JSON_OBJECT()
+SELECT JSON_OBJECT();
+ json_object
+-------------
+ {}
+(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: argument 1 cannot be null
+HINT: Object keys should be text.
+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 value
+SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+ERROR: duplicate JSON object key value
+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 value
+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)
+
+SELECT
+ JSON_ARRAYAGG(bar),
+ JSON_ARRAYAGG(bar RETURNING jsonb),
+ JSON_ARRAYAGG(bar ABSENT ON NULL),
+ JSON_ARRAYAGG(bar ABSENT ON NULL RETURNING jsonb),
+ JSON_ARRAYAGG(bar NULL ON NULL),
+ JSON_ARRAYAGG(bar NULL ON NULL RETURNING jsonb),
+ JSON_ARRAYAGG(foo),
+ JSON_ARRAYAGG(foo RETURNING jsonb),
+ JSON_ARRAYAGG(foo ORDER BY bar) FILTER (WHERE bar > 2),
+ JSON_ARRAYAGG(foo ORDER BY bar RETURNING jsonb) FILTER (WHERE bar > 2)
+FROM
+ (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL)) foo(bar);
+ json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg
+-----------------+-----------------+-----------------+-----------------+-----------------------------------------+-----------------------------------------+-----------------+--------------------------------------------------------------------------------------------------------------------------+---------------+--------------------------------------
+ [3, 1, 5, 2, 4] | [3, 1, 5, 2, 4] | [3, 1, 5, 2, 4] | [3, 1, 5, 2, 4] | [null, 3, 1, null, null, 5, 2, 4, null] | [null, 3, 1, null, null, 5, 2, 4, null] | [{"bar":null}, +| [{"bar": null}, {"bar": 3}, {"bar": 1}, {"bar": null}, {"bar": null}, {"bar": 5}, {"bar": 2}, {"bar": 4}, {"bar": null}] | [{"bar":3}, +| [{"bar": 3}, {"bar": 4}, {"bar": 5}]
+ | | | | | | {"bar":3}, +| | {"bar":4}, +|
+ | | | | | | {"bar":1}, +| | {"bar":5}] |
+ | | | | | | {"bar":null}, +| | |
+ | | | | | | {"bar":null}, +| | |
+ | | | | | | {"bar":5}, +| | |
+ | | | | | | {"bar":2}, +| | |
+ | | | | | | {"bar":4}, +| | |
+ | | | | | | {"bar":null}] | | |
+(1 row)
+
+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: field name must not be null
+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 value
+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 value
+-- 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.i : ('111'::text || i.i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i.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.i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i.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;
+-- IS JSON predicate
+SELECT NULL IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL IS NOT JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::json IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::jsonb IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::text IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::bytea IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::int IS JSON;
+ERROR: cannot use type integer in IS JSON predicate
+SELECT '' IS JSON;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT bytea '\x00' IS JSON;
+ERROR: invalid byte sequence for encoding "UTF8": 0x00
+CREATE TABLE test_is_json (js text);
+INSERT INTO test_is_json VALUES
+ (NULL),
+ (''),
+ ('123'),
+ ('"aaa "'),
+ ('true'),
+ ('null'),
+ ('[]'),
+ ('[1, "2", {}]'),
+ ('{}'),
+ ('{ "a": 1, "b": null }'),
+ ('{ "a": 1, "a": null }'),
+ ('{ "a": 1, "b": [{ "a": 1 }, { "a": 2 }] }'),
+ ('{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }'),
+ ('aaa'),
+ ('{a:1}'),
+ ('["a",]');
+SELECT
+ js,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ test_is_json;
+ js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE
+-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------
+ | | | | | | | |
+ | f | t | f | f | f | f | f | f
+ 123 | t | f | t | f | f | t | t | t
+ "aaa " | t | f | t | f | f | t | t | t
+ true | t | f | t | f | f | t | t | t
+ null | t | f | t | f | f | t | t | t
+ [] | t | f | t | f | t | f | t | t
+ [1, "2", {}] | t | f | t | f | t | f | t | t
+ {} | t | f | t | t | f | f | t | t
+ { "a": 1, "b": null } | t | f | t | t | f | f | t | t
+ { "a": 1, "a": null } | t | f | t | t | f | f | t | f
+ { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t
+ { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f
+ aaa | f | t | f | f | f | f | f | f
+ {a:1} | f | t | f | f | f | f | f | f
+ ["a",] | f | t | f | f | f | f | f | f
+(16 rows)
+
+SELECT
+ js,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js::json FROM test_is_json WHERE js IS JSON) foo(js);
+ js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE
+-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------
+ 123 | t | f | t | f | f | t | t | t
+ "aaa " | t | f | t | f | f | t | t | t
+ true | t | f | t | f | f | t | t | t
+ null | t | f | t | f | f | t | t | t
+ [] | t | f | t | f | t | f | t | t
+ [1, "2", {}] | t | f | t | f | t | f | t | t
+ {} | t | f | t | t | f | f | t | t
+ { "a": 1, "b": null } | t | f | t | t | f | f | t | t
+ { "a": 1, "a": null } | t | f | t | t | f | f | t | f
+ { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t
+ { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f
+(11 rows)
+
+SELECT
+ js0,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js, js::bytea FROM test_is_json WHERE js IS JSON) foo(js0, js);
+ js0 | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE
+-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------
+ 123 | t | f | t | f | f | t | t | t
+ "aaa " | t | f | t | f | f | t | t | t
+ true | t | f | t | f | f | t | t | t
+ null | t | f | t | f | f | t | t | t
+ [] | t | f | t | f | t | f | t | t
+ [1, "2", {}] | t | f | t | f | t | f | t | t
+ {} | t | f | t | t | f | f | t | t
+ { "a": 1, "b": null } | t | f | t | t | f | f | t | t
+ { "a": 1, "a": null } | t | f | t | t | f | f | t | f
+ { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t
+ { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f
+(11 rows)
+
+SELECT
+ js,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js::jsonb FROM test_is_json WHERE js IS JSON) foo(js);
+ js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE
+-------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------
+ 123 | t | f | t | f | f | t | t | t
+ "aaa " | t | f | t | f | f | t | t | t
+ true | t | f | t | f | f | t | t | t
+ null | t | f | t | f | f | t | t | t
+ [] | t | f | t | f | t | f | t | t
+ [1, "2", {}] | t | f | t | f | t | f | t | t
+ {} | t | f | t | t | f | f | t | t
+ {"a": 1, "b": null} | t | f | t | t | f | f | t | t
+ {"a": null} | t | f | t | t | f | f | t | t
+ {"a": 1, "b": [{"a": 1}, {"a": 2}]} | t | f | t | t | f | f | t | t
+ {"a": 1, "b": [{"a": 2, "b": 0}]} | t | f | t | t | f | f | t | t
+(11 rows)
+
+-- Test IS JSON deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i;
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------
+ Function Scan on pg_catalog.generate_series i
+ Output: ('1'::text IS JSON), (('1'::text || (i)::text) IS JSON SCALAR), (NOT ('[]'::text IS JSON ARRAY)), ('{}'::text IS JSON OBJECT WITH UNIQUE KEYS)
+ Function Call: generate_series(1, 3)
+(3 rows)
+
+CREATE VIEW is_json_view AS
+SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i;
+\sv is_json_view
+CREATE OR REPLACE VIEW public.is_json_view AS
+ SELECT '1'::text IS JSON AS "any",
+ ('1'::text || i.i) IS JSON SCALAR AS scalar,
+ NOT '[]'::text IS JSON ARRAY AS "array",
+ '{}'::text IS JSON OBJECT WITH UNIQUE KEYS AS object
+ FROM generate_series(1, 3) i(i)
+DROP VIEW is_json_view;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0..810450164d 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -108,7 +108,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 json_sqljson jsonb_sqljson
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/sql/json_sqljson.sql b/src/test/regress/sql/json_sqljson.sql
new file mode 100644
index 0000000000..4f30fa46b9
--- /dev/null
+++ b/src/test/regress/sql/json_sqljson.sql
@@ -0,0 +1,11 @@
+-- JSON_EXISTS
+
+SELECT JSON_EXISTS(NULL FORMAT JSON, '$');
+
+-- JSON_VALUE
+
+SELECT JSON_VALUE(NULL FORMAT JSON, '$');
+
+-- JSON_QUERY
+
+SELECT JSON_QUERY(NULL FORMAT JSON, '$');
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 5016f29c15..4af2a0cb11 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1482,3 +1482,41 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- test mapping of jsonb to SQL/JSON JSON type
+select json(' { "aa": 1, "b" : 2 }');
+select json ' { "aa": 1, "b" : 2 }';
+select pg_catalog.json ' { "aa": 1, "b" : 2 }';
+
+create table test_json_as_json (js json, jb jsonb);
+\d test_json_as_json
+
+set sql_json = jsonb;
+
+select json(' { "aa": 1, "b" : 2 }');
+select json ' { "aa": 1, "b" : 2 }';
+select pg_catalog.json ' { "aa": 1, "b" : 2 }';
+
+\d test_json_as_json
+
+create table test_json_as_jsonb (js json, jb jsonb, jt pg_catalog.json);
+\d test_json_as_jsonb
+
+insert into test_json_as_jsonb values ('{ "a" : 1 }', '{ "a" : 1 }', '{ "a" : 1 }');
+
+select * from test_json_as_jsonb;
+
+select jsonb_object_field(js, 'a') from test_json_as_jsonb;
+select jsonb_object_field(jb, 'a') from test_json_as_jsonb;
+select jsonb_object_field(jt, 'a') from test_json_as_jsonb;
+select json_object_field(jt, 'a') from test_json_as_jsonb;
+
+set sql_json = json;
+\d test_json_as_jsonb
+
+select * from test_json_as_jsonb;
+
+select jsonb_object_field(js, 'a') from test_json_as_jsonb;
+select jsonb_object_field(jb, 'a') from test_json_as_jsonb;
+select jsonb_object_field(jt, 'a') from test_json_as_jsonb;
+select json_object_field(jt, 'a') from test_json_as_jsonb;
diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql
new file mode 100644
index 0000000000..00a067a06a
--- /dev/null
+++ b/src/test/regress/sql/jsonb_sqljson.sql
@@ -0,0 +1,317 @@
+-- JSON_EXISTS
+
+SELECT JSON_EXISTS(NULL::jsonb, '$');
+
+SELECT JSON_EXISTS(jsonb '[]', '$');
+SELECT JSON_EXISTS(JSON_OBJECT(RETURNING jsonb), '$');
+
+SELECT JSON_EXISTS(jsonb '1', '$');
+SELECT JSON_EXISTS(jsonb 'null', '$');
+SELECT JSON_EXISTS(jsonb '[]', '$');
+
+SELECT JSON_EXISTS(jsonb '1', '$.a');
+SELECT JSON_EXISTS(jsonb '1', 'strict $.a');
+SELECT JSON_EXISTS(jsonb '1', 'strict $.a' ERROR ON ERROR);
+SELECT JSON_EXISTS(jsonb 'null', '$.a');
+SELECT JSON_EXISTS(jsonb '[]', '$.a');
+SELECT JSON_EXISTS(jsonb '[1, "aaa", {"a": 1}]', 'strict $.a');
+SELECT JSON_EXISTS(jsonb '[1, "aaa", {"a": 1}]', 'lax $.a');
+SELECT JSON_EXISTS(jsonb '{}', '$.a');
+SELECT JSON_EXISTS(jsonb '{"b": 1, "a": 2}', '$.a');
+
+SELECT JSON_EXISTS(jsonb '1', '$.a.b');
+SELECT JSON_EXISTS(jsonb '{"a": {"b": 1}}', '$.a.b');
+SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.a.b');
+
+SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING 1 AS x);
+SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING '1' AS x);
+SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 2 AS y);
+SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 1 AS y);
+
+-- extension: boolean expressions
+SELECT JSON_EXISTS(jsonb '1', '$ > 2');
+SELECT JSON_EXISTS(jsonb '1', '$.a > 2' ERROR ON ERROR);
+
+-- extension: RETURNING clause
+SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING bool);
+SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING bool);
+SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING int);
+SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING int);
+SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING text);
+SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING text);
+SELECT JSON_EXISTS(jsonb '1', 'strict $[1]' RETURNING text FALSE ON ERROR);
+SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb);
+SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4);
+
+
+-- JSON_VALUE
+
+SELECT JSON_VALUE(NULL::jsonb, '$');
+
+SELECT JSON_VALUE(jsonb 'null', '$');
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING int);
+
+SELECT JSON_VALUE(jsonb 'true', '$');
+SELECT JSON_VALUE(jsonb 'true', '$' RETURNING bool);
+
+SELECT JSON_VALUE(jsonb '123', '$');
+SELECT JSON_VALUE(jsonb '123', '$' RETURNING int) + 234;
+SELECT JSON_VALUE(jsonb '123', '$' RETURNING text);
+/* jsonb bytea ??? */
+SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR);
+
+SELECT JSON_VALUE(jsonb '1.23', '$');
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING int);
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric);
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING int ERROR ON ERROR);
+
+SELECT JSON_VALUE(jsonb '"aaa"', '$');
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING text);
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(5));
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(2));
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json);
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb);
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json ERROR ON ERROR);
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb ERROR ON ERROR);
+SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING json);
+SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING jsonb);
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int);
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int ERROR ON ERROR);
+SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR);
+SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234;
+
+SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9;
+
+-- Test NULL checks execution in domain types
+CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL;
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null);
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null NULL ON ERROR);
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR);
+
+SELECT JSON_VALUE(jsonb '[]', '$');
+SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR);
+SELECT JSON_VALUE(jsonb '{}', '$');
+SELECT JSON_VALUE(jsonb '{}', '$' ERROR ON ERROR);
+
+SELECT JSON_VALUE(jsonb '1', '$.a');
+SELECT JSON_VALUE(jsonb '1', 'strict $.a' ERROR ON ERROR);
+SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 'error' ON ERROR);
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON ERROR);
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY ERROR ON ERROR);
+SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 2 ON ERROR);
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT 2 ON ERROR);
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON ERROR);
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR);
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR);
+SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR);
+
+SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' ERROR ON ERROR);
+SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' DEFAULT '0' ON ERROR);
+SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int ERROR ON ERROR);
+SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR);
+SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR);
+
+SELECT
+ x,
+ JSON_VALUE(
+ jsonb '{"a": 1, "b": 2}',
+ '$.* ? (@ > $x)' PASSING x AS x
+ RETURNING int
+ DEFAULT -1 ON EMPTY
+ DEFAULT -2 ON ERROR
+ ) y
+FROM
+ generate_series(0, 2) x;
+
+SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a);
+SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point);
+
+-- Test timestamptz passing and output
+SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
+SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamptz);
+SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamp);
+SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json);
+SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb);
+
+-- JSON_QUERY
+
+SELECT
+ JSON_QUERY(js, '$'),
+ JSON_QUERY(js, '$' WITHOUT WRAPPER),
+ JSON_QUERY(js, '$' WITH CONDITIONAL WRAPPER),
+ JSON_QUERY(js, '$' WITH UNCONDITIONAL ARRAY WRAPPER),
+ JSON_QUERY(js, '$' WITH ARRAY WRAPPER)
+FROM
+ (VALUES
+ (jsonb 'null'),
+ ('12.3'),
+ ('true'),
+ ('"aaa"'),
+ ('[1, null, "2"]'),
+ ('{"a": 1, "b": [2]}')
+ ) foo(js);
+
+SELECT
+ JSON_QUERY(js, 'strict $[*]') AS "unspec",
+ JSON_QUERY(js, 'strict $[*]' WITHOUT WRAPPER) AS "without",
+ JSON_QUERY(js, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond",
+ JSON_QUERY(js, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond",
+ JSON_QUERY(js, 'strict $[*]' WITH ARRAY WRAPPER) AS "with"
+FROM
+ (VALUES
+ (jsonb '1'),
+ ('[]'),
+ ('[null]'),
+ ('[12.3]'),
+ ('[true]'),
+ ('["aaa"]'),
+ ('[[1, 2, 3]]'),
+ ('[{"a": 1, "b": [2]}]'),
+ ('[1, "2", null, [3]]')
+ ) foo(js);
+
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text);
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES);
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES ON SCALAR STRING);
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES);
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING);
+SELECT JSON_QUERY(jsonb '"aaa"', '$' OMIT QUOTES ERROR ON ERROR);
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES ERROR ON ERROR);
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR);
+
+-- QUOTES behavior should not be specified when WITH WRAPPER used:
+-- Should fail
+SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES);
+SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER KEEP QUOTES);
+SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTES);
+SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES);
+-- Should succeed
+SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER OMIT QUOTES);
+SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER KEEP QUOTES);
+
+SELECT JSON_QUERY(jsonb '[]', '$[*]');
+SELECT JSON_QUERY(jsonb '[]', '$[*]' NULL ON EMPTY);
+SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ON EMPTY);
+SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ARRAY ON EMPTY);
+SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY OBJECT ON EMPTY);
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY);
+SELECT JSON_QUERY(jsonb '[]', '$[*]' DEFAULT '"empty"' ON EMPTY);
+
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY NULL ON ERROR);
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR);
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR);
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY ERROR ON ERROR);
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON ERROR);
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' ERROR ON ERROR);
+SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' DEFAULT '"empty"' ON ERROR);
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json);
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json FORMAT JSON);
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING jsonb);
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING jsonb FORMAT JSON);
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text);
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING char(10));
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING char(3));
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text FORMAT JSON);
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea);
+SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea FORMAT JSON);
+
+SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR);
+SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT ON ERROR);
+SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR);
+SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING jsonb EMPTY OBJECT ON ERROR);
+
+SELECT
+ x, y,
+ JSON_QUERY(
+ jsonb '[1,2,3,4,5,null]',
+ '$[*] ? (@ >= $x && @ <= $y)'
+ PASSING x AS x, y AS y
+ WITH CONDITIONAL WRAPPER
+ EMPTY ARRAY ON EMPTY
+ ) list
+FROM
+ generate_series(0, 4) x,
+ generate_series(0, 4) y;
+
+-- Extension: record types returning
+CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]);
+CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]);
+
+SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec);
+SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa);
+SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca);
+
+-- Extension: array types returning
+SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER);
+SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[]));
+
+-- Extension: domain types returning
+SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null);
+SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null);
+
+-- Test timestamptz passing and output
+SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
+SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json);
+SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb);
+
+-- Test constraints
+
+CREATE TABLE test_jsonb_constraints (
+ js text,
+ i int,
+ x jsonb DEFAULT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH WRAPPER)
+ CONSTRAINT test_jsonb_constraint1
+ CHECK (js IS JSON)
+ CONSTRAINT test_jsonb_constraint2
+ CHECK (JSON_EXISTS(js::jsonb, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr))
+ CONSTRAINT test_jsonb_constraint3
+ CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i)
+ CONSTRAINT test_jsonb_constraint4
+ CHECK (JSON_QUERY(js::jsonb, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]')
+ CONSTRAINT test_jsonb_constraint5
+ CHECK (JSON_QUERY(js::jsonb, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a' COLLATE "C")
+ CONSTRAINT test_jsonb_constraint6
+ CHECK (JSON_EXISTS(js::jsonb, 'strict $.a' RETURNING int TRUE ON ERROR) < 2)
+);
+
+\d test_jsonb_constraints
+
+SELECT check_clause
+FROM information_schema.check_constraints
+WHERE constraint_name LIKE 'test_jsonb_constraint%';
+
+SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'test_jsonb_constraints'::regclass;
+
+INSERT INTO test_jsonb_constraints VALUES ('', 1);
+INSERT INTO test_jsonb_constraints VALUES ('1', 1);
+INSERT INTO test_jsonb_constraints VALUES ('[]');
+INSERT INTO test_jsonb_constraints VALUES ('{"b": 1}', 1);
+INSERT INTO test_jsonb_constraints VALUES ('{"a": 1}', 1);
+INSERT INTO test_jsonb_constraints VALUES ('{"a": 7}', 1);
+INSERT INTO test_jsonb_constraints VALUES ('{"a": 10}', 1);
+
+DROP TABLE test_jsonb_constraints;
+
+-- Test mutabilily od query functions
+CREATE TABLE test_jsonb_mutability(js jsonb);
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a[0]'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime()'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@ < $.datetime())'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime())'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime("HH:MI TZH"))'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("HH:MI TZH"))'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI") < $.datetime("YY-MM-DD HH:MI"))'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("YY-MM-DD HH:MI"))'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $x' PASSING '12:34'::timetz AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $y' PASSING '12:34'::timetz AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '12:34'::timetz AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '1234'::int AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() ? (@ == $x)' PASSING '12:34'::time AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("YY-MM-DD") ? (@ == $x)' PASSING '2020-07-14'::date AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x));
+DROP TABLE test_jsonb_mutability;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 5a9c479692..7a849ae363 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -854,8 +854,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
index 0000000000..4ff6076763
--- /dev/null
+++ b/src/test/regress/sql/sqljson.sql
@@ -0,0 +1,467 @@
+-- JSON()
+SELECT JSON();
+SELECT JSON(NULL);
+SELECT JSON('{ "a" : 1 } ');
+SELECT JSON('{ "a" : 1 } ' FORMAT JSON);
+SELECT JSON('{ "a" : 1 } ' FORMAT JSON ENCODING UTF8);
+SELECT JSON('{ "a" : 1 } '::bytea FORMAT JSON ENCODING UTF8);
+SELECT pg_typeof(JSON('{ "a" : 1 } '));
+
+SELECT JSON(' 1 '::json);
+SELECT JSON(' 1 '::jsonb);
+SELECT JSON(' 1 '::json WITH UNIQUE KEYS);
+SELECT JSON(123);
+
+SELECT JSON('{"a": 1, "a": 2}');
+SELECT JSON('{"a": 1, "a": 2}' WITH UNIQUE KEYS);
+SELECT JSON('{"a": 1, "a": 2}' WITHOUT UNIQUE KEYS);
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123');
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' FORMAT JSON);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON ENCODING UTF8);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITH UNIQUE KEYS);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITHOUT UNIQUE KEYS);
+
+SELECT JSON('123' RETURNING text);
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123');
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb);
+SELECT pg_typeof(JSON('123'));
+SELECT pg_typeof(JSON('123' RETURNING json));
+SELECT pg_typeof(JSON('123' RETURNING jsonb));
+
+SET sql_json = jsonb;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123');
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING pg_catalog.json);
+SELECT pg_typeof(JSON('123'));
+SELECT pg_typeof(JSON('123' RETURNING json));
+SELECT pg_typeof(JSON('123' RETURNING jsonb));
+SELECT pg_typeof(JSON('123' RETURNING pg_catalog.json));
+
+SET sql_json = json;
+
+-- JSON_SCALAR()
+SELECT JSON_SCALAR();
+SELECT JSON_SCALAR(NULL);
+SELECT JSON_SCALAR(NULL::int);
+SELECT JSON_SCALAR(123);
+SELECT JSON_SCALAR(123.45);
+SELECT JSON_SCALAR(123.45::numeric);
+SELECT JSON_SCALAR(true);
+SELECT JSON_SCALAR(false);
+SELECT JSON_SCALAR(' 123.45');
+SELECT JSON_SCALAR('2020-06-07'::date);
+SELECT JSON_SCALAR('2020-06-07 01:02:03'::timestamp);
+SELECT JSON_SCALAR('{}'::json);
+SELECT JSON_SCALAR('{}'::jsonb);
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123');
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb);
+
+SET sql_json = jsonb;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING pg_catalog.json);
+
+SET sql_json = json;
+
+-- JSON_SERIALIZE()
+SELECT JSON_SERIALIZE();
+SELECT JSON_SERIALIZE(NULL);
+SELECT JSON_SERIALIZE(JSON('{ "a" : 1 } '));
+SELECT JSON_SERIALIZE('{ "a" : 1 } ');
+SELECT JSON_SERIALIZE('1');
+SELECT JSON_SERIALIZE('1' FORMAT JSON);
+SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING bytea);
+SELECT pg_typeof(JSON_SERIALIZE(NULL));
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SERIALIZE('{}');
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SERIALIZE('{}' RETURNING bytea);
+
+-- JSON_OBJECT()
+SELECT JSON_OBJECT();
+SELECT JSON_OBJECT(RETURNING json);
+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);
+
+SELECT
+ JSON_ARRAYAGG(bar),
+ JSON_ARRAYAGG(bar RETURNING jsonb),
+ JSON_ARRAYAGG(bar ABSENT ON NULL),
+ JSON_ARRAYAGG(bar ABSENT ON NULL RETURNING jsonb),
+ JSON_ARRAYAGG(bar NULL ON NULL),
+ JSON_ARRAYAGG(bar NULL ON NULL RETURNING jsonb),
+ JSON_ARRAYAGG(foo),
+ JSON_ARRAYAGG(foo RETURNING jsonb),
+ JSON_ARRAYAGG(foo ORDER BY bar) FILTER (WHERE bar > 2),
+ JSON_ARRAYAGG(foo ORDER BY bar RETURNING jsonb) FILTER (WHERE bar > 2)
+FROM
+ (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL)) foo(bar);
+
+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;
+
+-- IS JSON predicate
+SELECT NULL IS JSON;
+SELECT NULL IS NOT JSON;
+SELECT NULL::json IS JSON;
+SELECT NULL::jsonb IS JSON;
+SELECT NULL::text IS JSON;
+SELECT NULL::bytea IS JSON;
+SELECT NULL::int IS JSON;
+
+SELECT '' IS JSON;
+
+SELECT bytea '\x00' IS JSON;
+
+CREATE TABLE test_is_json (js text);
+
+INSERT INTO test_is_json VALUES
+ (NULL),
+ (''),
+ ('123'),
+ ('"aaa "'),
+ ('true'),
+ ('null'),
+ ('[]'),
+ ('[1, "2", {}]'),
+ ('{}'),
+ ('{ "a": 1, "b": null }'),
+ ('{ "a": 1, "a": null }'),
+ ('{ "a": 1, "b": [{ "a": 1 }, { "a": 2 }] }'),
+ ('{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }'),
+ ('aaa'),
+ ('{a:1}'),
+ ('["a",]');
+
+SELECT
+ js,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ test_is_json;
+
+SELECT
+ js,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js::json FROM test_is_json WHERE js IS JSON) foo(js);
+
+SELECT
+ js0,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js, js::bytea FROM test_is_json WHERE js IS JSON) foo(js0, js);
+
+SELECT
+ js,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js::jsonb FROM test_is_json WHERE js IS JSON) foo(js);
+
+-- Test IS JSON deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i;
+
+CREATE VIEW is_json_view AS
+SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i;
+
+\sv is_json_view
+
+DROP VIEW is_json_view;
--
2.25.4
--------------468B109EE30CA6EBDFFE9C87
Content-Type: text/x-patch; charset=UTF-8;
name="0002-JSON_TABLE-v52.patch"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="0002-JSON_TABLE-v52.patch"
=46rom 641878c1496916c2b6b24a095ff3a88a7f22290b Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 16 Sep 2021 10:31:39 -0400
Subject: [PATCH 2/4] JSON_TABLE
---
doc/src/sgml/func.sgml | 330 ++++++++++++
src/backend/commands/explain.c | 4 +-
src/backend/executor/execExpr.c | 1 +
src/backend/executor/execExprInterp.c | 19 +-
src/backend/executor/nodeTableFuncscan.c | 23 +-
src/backend/nodes/copyfuncs.c | 85 +++
src/backend/nodes/equalfuncs.c | 29 +
src/backend/nodes/nodeFuncs.c | 27 +
src/backend/nodes/outfuncs.c | 29 +
src/backend/nodes/readfuncs.c | 31 ++
src/backend/parser/Makefile | 1 +
src/backend/parser/gram.y | 199 ++++++-
src/backend/parser/parse_clause.c | 10 +-
src/backend/parser/parse_expr.c | 23 +-
src/backend/parser/parse_jsontable.c | 467 ++++++++++++++++
src/backend/parser/parse_relation.c | 3 +-
src/backend/parser/parse_target.c | 3 +
src/backend/utils/adt/jsonpath_exec.c | 436 +++++++++++++++
src/backend/utils/adt/ruleutils.c | 227 +++++++-
src/backend/utils/misc/queryjumble.c | 2 +
src/include/executor/execExpr.h | 4 +
src/include/nodes/nodes.h | 5 +
src/include/nodes/parsenodes.h | 48 ++
src/include/nodes/primnodes.h | 37 +-
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_clause.h | 3 +
src/include/utils/jsonpath.h | 4 +
src/test/regress/expected/json_sqljson.out | 5 +
src/test/regress/expected/jsonb_sqljson.out | 562 ++++++++++++++++++++
src/test/regress/sql/json_sqljson.sql | 4 +
src/test/regress/sql/jsonb_sqljson.sql | 284 ++++++++++
src/tools/pgindent/typedefs.list | 9 +
32 files changed, 2885 insertions(+), 32 deletions(-)
create mode 100644 src/backend/parser/parse_jsontable.c
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a75b92bdb9..e86c8992c3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18524,6 +18524,11 @@ FROM films AS f;
<link linkend=3D"functions-jsonquery"><literal>JSON_QUERY</litera=
l></link>
</para>
</listitem>
+ <listitem>
+ <para>
+ <link linkend=3D"functions-jsontable"><literal>JSON_TABLE</litera=
l></link>
+ </para>
+ </listitem>
</itemizedlist>
=20
<para>
@@ -19194,6 +19199,331 @@ FROM
</sect5>
</sect4>
=20
+ <sect4 id=3D"functions-jsontable">
+ <title><literal>JSON_TABLE</literal></title>
+ <indexterm><primary>json_table</primary></indexterm>
+
+<synopsis>
+JSON_TABLE (
+ <replaceable>context_item</replaceable>, <replaceable>path_expression<=
/replaceable> <optional> PASSING { <replaceable>value</replaceable> AS <r=
eplaceable>varname</replaceable> } <optional>, ...</optional> </optional>=
+ COLUMNS ( <replaceable class=3D"parameter">json_table_column</replacea=
ble> <optional>, ...</optional> )
+)
+<phrase>
+where <replaceable class=3D"parameter">json_table_column</replaceable> i=
s:
+</phrase>
+ <replaceable>name</replaceable> <replaceable>type</replaceable> <opt=
ional> PATH <replaceable>json_path_specification</replaceable> </optional=
>
+ <optional> { WITHOUT | WITH { CONDITIONAL | <optional>UNCONDITIO=
NAL</optional> } } <optional> ARRAY </optional> WRAPPER </optional>
+ <optional> { KEEP | OMIT } QUOTES <optional> ON SCALAR STRING </=
optional> </optional>
+ <optional> { ERROR | NULL | DEFAULT <replaceable>expression</rep=
laceable> } ON EMPTY </optional>
+ <optional> { ERROR | NULL | DEFAULT <replaceable>expression</rep=
laceable> } ON ERROR </optional>
+ | <replaceable>name</replaceable> <replaceable>type</replaceable> FORM=
AT <replaceable>json_representation</replaceable>
+ <optional> PATH <replaceable>json_path_specification</replaceabl=
e> </optional>
+ <optional> { WITHOUT | WITH { CONDITIONAL | <optional>UNCONDITIO=
NAL</optional> } } <optional> ARRAY </optional> WRAPPER </optional>
+ <optional> { KEEP | OMIT } QUOTES <optional> ON SCALAR STRING </=
optional> </optional>
+ <optional> { ERROR | NULL | EMPTY { ARRAY | OBJECT } | DEFAULT <=
replaceable>expression</replaceable> } ON EMPTY </optional>
+ <optional> { ERROR | NULL | EMPTY { ARRAY | OBJECT } | DEFAULT <=
replaceable>expression</replaceable> } ON ERROR </optional>
+ | <replaceable>name</replaceable> <replaceable>type</replaceable> EXIS=
TS <optional> PATH <replaceable>json_path_specification</replaceable> </o=
ptional>
+ <optional> { ERROR | TRUE | FALSE | UNKNOWN } ON ERROR </optiona=
l>
+ | NESTED PATH <replaceable>json_path_specification</replaceable> <opti=
onal> AS <replaceable>path_name</replaceable> </optional>
+ COLUMNS ( <replaceable>json_table_column</replaceable> <optional=
>, ...</optional> )
+ | <replaceable>name</replaceable> FOR ORDINALITY
+
+</synopsis>
+
+ <sect5>
+ <title>Description</title>
+
+ <para>
+ <function>JSON_TABLE</function> function queries <acronym>JSON</ac=
ronym> data
+ and presents the results as a relational view, which can be access=
ed as a
+ regular SQL table. You can only use <function>JSON_TABLE</function=
> inside the
+ <literal>FROM</literal> clause of the <literal>SELECT</literal> st=
atement
+ for an SQL table.
+ </para>
+
+ <para>
+ Taking JSON data as input, <function>JSON_TABLE</function> uses
+ a path expression to extract a part of the provided data that
+ will be used as a <firstterm>row pattern</firstterm> for the
+ constructed view. Each SQL/JSON item at the top level of the row p=
attern serves
+ as the source for a separate row in the constructed relational vie=
w.
+ </para>
+
+ <para>
+ To split the row pattern into columns, <function>JSON_TABLE</funct=
ion>
+ provides the <literal>COLUMNS</literal> clause that defines the
+ schema of the created view. For each column to be constructed,
+ this clause provides a separate path expression that evaluates
+ the row pattern, extracts a JSON item, and returns it as a
+ separate SQL value for the specified column. If the required value=
+ is stored in a nested level of the row pattern, it can be extracte=
d
+ using the <literal>NESTED PATH</literal> subclause. Joining the
+ columns returned by <literal>NESTED PATH</literal> can add multipl=
e
+ new rows to the constructed view. Such rows are called
+ <firstterm>child rows</firstterm>, as opposed to the <firstterm>pa=
rent row</firstterm>
+ that generates them.
+ </para>
+
+ <para>
+ The rows produced by <function>JSON_TABLE</function> are laterally=
+ joined to the row that generated them, so you do not have to expli=
citly join
+ the constructed view with the original table holding <acronym>JSON=
</acronym>
+ data.
+ </para>
+
+ <para>
+ Each <literal>NESTED PATH</literal> clause can generate one or mor=
e
+ columns, which are considered to be <firstterm>siblings</firstterm=
>
+ to each other. In relation to the columns returned directly from t=
he row
+ expression or by the <literal>NESTED PATH</literal> clause of a
+ higher level, these columns are <firstterm>child</firstterm> colum=
ns.
+ Sibling columns are always joined first. Once they are processed,
+ the resulting rows are joined to the parent row.
+ </para>
+
+ <para>
+ Columns with parent/child relationship are joined using
+ <literal>LEFT OUTER JOIN</literal>, so that the parent row
+ is always included into the output even if it does not have any ch=
ild rows
+ after joining the data returned by <literal>NESTED PATH</literal>,=
+ with NULL values inserted into the child columns if the correspond=
ing
+ values are missing.
+ </para>
+
+ <para>
+ Sibling columns are joined using
+ <literal>FULL OUTER JOIN ON FALSE</literal>, so that both parent a=
nd child
+ rows are included into the output, with NULL values inserted
+ into both child and parrent columns for all missing values.
+ </para>
+
+ </sect5>
+ <sect5>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal><replaceable>context_item</replaceable>, <replaceable>path=
_expression</replaceable> <optional> AS <replaceable>json_path_name</repl=
aceable> </optional> <optional> PASSING { <replaceable>value</replaceable=
> AS <replaceable>varname</replaceable> } <optional>, ...</optional></opt=
ional></literal>
+ </term>
+ <listitem>
+
+ <para>
+ The input data to query, the JSON path expression defining the quer=
y,
+ and an optional <literal>PASSING</literal> clause, as described in
+ <xref linkend=3D"sqljson-input-clause"/>. The result of the input d=
ata
+ evaluation is called the <firstterm>row pattern</firstterm>. The ro=
w
+ pattern is used as the source for row values in the constructed vie=
w.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>COLUMNS( <replaceable class=3D"parameter">json_table_colum=
n</replaceable> <optional>, ...</optional> )</literal>
+ </term>
+ <listitem>
+
+ <para>
+ The <literal>COLUMNS</literal> clause defining the schema of the
+ constructed view. In this clause, you must specify all the columns
+ to be filled with SQL/JSON items.
+ The <replaceable class=3D"parameter">json_table_column</replaceable=
>
+ expression has the following syntax variants:
+ </para>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal><replaceable>name</replaceable> <replaceable>type</replace=
able>
+ <optional> PATH <replaceable>json_path_specification</replacea=
ble> </optional></literal>
+ </term>
+ <listitem>
+
+ <para>
+ Inserts a single SQL/JSON item into each row of
+ the specified column.
+ </para>
+ <para>
+ The provided <literal>PATH</literal> expression parses the
+ row pattern defined by <replaceable>json_api_common_syntax</replace=
able>
+ and fills the column with produced SQL/JSON items, one for each row=
=2E
+ If the <literal>PATH</literal> expression is omitted,
+ <function>JSON_TABLE</function> uses the
+ <literal>$.<replaceable>name</replaceable></literal> path expressio=
n,
+ where <replaceable>name</replaceable> is the provided column name.
+ In this case, the column name must correspond to one of the
+ keys within the SQL/JSON item produced by the row pattern.
+ </para>
+ <para>
+ Internally, <xref linkend=3D"functions-jsonvalue"/> and
+ <xref linkend=3D"functions-jsonquery"/> are used to produce result=
ing values.
+ <xref linkend=3D"functions-jsonquery"/> is used for JSON, array, a=
nd
+ composite column types, <xref linkend=3D"functions-jsonvalue"/> is=
used for
+ other types.
+ </para>
+ <para>
+ Optionally, you can add <literal>ON EMPTY</literal> and
+ <literal>ON ERROR</literal> clauses to define how to handle missing=
values
+ or structural errors.
+ <literal>WRAPPER</literal> and <literal>QUOTES</literal> clauses ca=
n only
+ be used with JSON, array, and composite types.
+ These clauses have the same syntax and semantics as in
+ <xref linkend=3D"functions-jsonvalue"/> and
+ <xref linkend=3D"functions-jsonquery"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal><replaceable>name</replaceable> <replaceable>type</replace=
able> FORMAT <replaceable>json_representation</replaceable>
+ <optional> PATH <replaceable>json_path_specification</replacea=
ble> </optional></literal>
+ </term>
+ <listitem>
+
+ <para>
+ Gerenates a column and inserts a composite SQL/JSON
+ item into each row of this column.
+ </para>
+ <para>
+ The provided <literal>PATH</literal> expression parses the
+ row pattern defined by <replaceable>json_api_common_syntax</replace=
able>
+ and fills the column with produced SQL/JSON items, one for each row=
=2E
+ If the <literal>PATH</literal> expression is omitted,
+ <function>JSON_TABLE</function> uses the
+ <literal>$.<replaceable>name</replaceable></literal> path expressio=
n,
+ where <replaceable>name</replaceable> is the provided column name.
+ In this case, the column name must correspond to one of the
+ keys within the SQL/JSON item produced by the row pattern.
+ </para>
+ <para>
+ Internally, <xref linkend=3D"functions-jsonquery"/> is used to pro=
duce
+ resulting values.
+ </para>
+ <para>
+ Optionally, you can add <literal>WRAPPER</literal>, <literal>QUOTES=
</literal>,
+ <literal>ON EMPTY</literal> and <literal>ON ERROR</literal> clauses=
+ to define additional settings for the returned SQL/JSON items.
+ These clauses have the same syntax and semantics as
+ in <xref linkend=3D"functions-jsonquery"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>
+ <replaceable>name</replaceable> <replaceable>type</replaceable>
+ EXISTS <optional> PATH <replaceable>json_path_specification</repl=
aceable> </optional>
+ </literal>
+ </term>
+ <listitem>
+
+ <para>
+ Gerenates a column and inserts a boolean item into each row of this=
column.
+ </para>
+ <para>
+ The provided <literal>PATH</literal> expression parses the
+ row pattern defined by <replaceable>json_api_common_syntax</replace=
able>,
+ checks whether any SQL/JSON items were returned, and fills the colu=
mn with
+ resulting boolean value, one for each row.
+ The specified <replaceable>type</replaceable> should have cast from=
+ <type>boolean</type>.
+ If the <literal>PATH</literal> expression is omitted,
+ <function>JSON_TABLE</function> uses the
+ <literal>$.<replaceable>name</replaceable></literal> path expressio=
n,
+ where <replaceable>name</replaceable> is the provided column name.
+ </para>
+ <para>
+ Optionally, you can add <literal>ON ERROR</literal> clause to defin=
e
+ error behavior. This clause have the same syntax and semantics as =
in
+ <xref linkend=3D"functions-jsonexists"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>NESTED PATH <replaceable>json_path_specification</replacea=
ble> <optional> AS <replaceable>json_path_name</replaceable> </optional>
+ COLUMNS ( <replaceable>json_table_column</replaceable> <option=
al>, ...</optional> )</literal>
+ </term>
+ <listitem>
+
+ <para>
+ Extracts SQL/JSON items from nested levels of the row pattern,
+ gerenates one or more columns as defined by the <literal>COLUMNS</l=
iteral>
+ subclause, and inserts the extracted SQL/JSON items into each row o=
f these columns.
+ The <replaceable>json_table_column</replaceable> expression in the
+ <literal>COLUMNS</literal> subclause uses the same syntax as in the=
+ parent <literal>COLUMNS</literal> clause.
+ </para>
+
+ <para>
+ The <literal>NESTED PATH</literal> syntax is recursive,
+ so you can go down multiple nested levels by specifying several
+ <literal>NESTED PATH</literal> subclauses within each other.
+ It allows to unnest the hierarchy of JSON objects and arrays
+ in a single function invocation rather than chaining several
+ <function>JSON_TABLE</function> expressions in an SQL statement.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal><replaceable>name</replaceable> FOR ORDINALITY</literal>
+ </term>
+ <listitem>
+
+ <para>
+ Adds an ordinality column that provides sequential row numbering.
+ You can have only one ordinality column per table. Row numbering
+ is 1-based. For child rows that result from the <literal>NESTED PAT=
H</literal>
+ clauses, the parent row number is repeated.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </sect5>
+
+ <sect5>
+ <title>Examples</title>
+
+ <para>
+ Query the <structname>my_films</structname> table holding
+ some JSON data about the films and create a view that
+ distributes the film genre, title, and director between separate c=
olumns:
+<screen>
+SELECT jt.* FROM=20
+ my_films,
+ JSON_TABLE ( js, '$.favorites[*]' COLUMNS (
+ id FOR ORDINALITY,
+ kind text PATH '$.kind',
+ NESTED PATH '$.films[*]' COLUMNS (
+ title text PATH '$.title',
+ director text PATH '$.director'))) AS jt;
+----+----------+------------------+-------------------
+ id | kind | title | director
+----+----------+------------------+-------------------
+ 1 | comedy | Bananas | Woody Allen
+ 1 | comedy | The Dinner Game | Francis Veber
+ 2 | horror | Psycho | Alfred Hitchcock
+ 3 | thriller | Vertigo | Hitchcock
+ 4 | drama | Yojimbo | Akira Kurosawa
+ (5 rows)
+</screen>
+ </para>
+
+ </sect5>
+ </sect4>
=20
</sect3>
=20
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explai=
n.c
index 10644dfac4..9ba9eb6d27 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -3784,7 +3784,9 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainStat=
e *es)
break;
case T_TableFuncScan:
Assert(rte->rtekind =3D=3D RTE_TABLEFUNC);
- objectname =3D "xmltable";
+ objectname =3D rte->tablefunc ?
+ rte->tablefunc->functype =3D=3D TFT_XMLTABLE ?
+ "xmltable" : "json_table" : NULL;
objecttag =3D "Table Function Name";
break;
case T_ValuesScan:
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execE=
xpr.c
index efcfde8c95..76f9ee6564 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2635,6 +2635,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
var->typmod =3D exprTypmod((Node *) argexpr);
var->estate =3D ExecInitExpr(argexpr, state->parent);
var->econtext =3D NULL;
+ var->mcxt =3D NULL;
var->evaluated =3D false;
var->value =3D (Datum) 0;
var->isnull =3D true;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor=
/execExprInterp.c
index 0d4d30853e..3be81abc91 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4608,6 +4608,7 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBeh=
avior *behavior,
=20
case JSON_BEHAVIOR_NULL:
case JSON_BEHAVIOR_UNKNOWN:
+ case JSON_BEHAVIOR_EMPTY:
*is_null =3D true;
return (Datum) 0;
=20
@@ -4700,8 +4701,14 @@ EvalJsonPathVar(void *cxt, char *varName, int varN=
ameLen,
=20
if (!var->evaluated)
{
+ MemoryContext oldcxt =3D var->mcxt ?
+ MemoryContextSwitchTo(var->mcxt) : NULL;
+
var->value =3D ExecEvalExpr(var->estate, var->econtext, &var->isnull);=
var->evaluated =3D true;
+
+ if (oldcxt)
+ MemoryContextSwitchTo(oldcxt);
}
=20
if (var->isnull)
@@ -4837,6 +4844,7 @@ ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalSte=
p *op,
/* Want to execute expressions inside function's memory context */
MemoryContextSwitchTo(oldcontext);
=20
+
PG_TRY();
{
res =3D func(op, econtext, res, resnull, p, error);
@@ -4849,6 +4857,7 @@ ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalSte=
p *op,
PG_CATCH();
{
ErrorData *edata;
+ int ecategory;
=20
/* Save error info in oldcontext */
MemoryContextSwitchTo(oldcontext);
@@ -4860,8 +4869,10 @@ ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalSt=
ep *op,
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner =3D oldowner;
=20
- if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) !=3D
- ERRCODE_DATA_EXCEPTION)
+ ecategory =3D ERRCODE_TO_CATEGORY(edata->sqlerrcode);
+
+ if (ecategory !=3D ERRCODE_DATA_EXCEPTION && /* jsonpath and other dat=
a errors */
+ ecategory !=3D ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION) /* domain erro=
rs */
ReThrowError(edata);
=20
res =3D (Datum) 0;
@@ -4987,6 +4998,10 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *ec=
ontext,
break;
}
=20
+ case IS_JSON_TABLE:
+ *resnull =3D false;
+ return item;
+
default:
elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
return (Datum) 0;
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/execu=
tor/nodeTableFuncscan.c
index 27dfa1b956..e4a5f1b6fe 100644
--- a/src/backend/executor/nodeTableFuncscan.c
+++ b/src/backend/executor/nodeTableFuncscan.c
@@ -28,6 +28,7 @@
#include "miscadmin.h"
#include "nodes/execnodes.h"
#include "utils/builtins.h"
+#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/xml.h"
@@ -161,8 +162,9 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *es=
tate, int eflags)
scanstate->ss.ps.qual =3D
ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps);
=20
- /* Only XMLTABLE is supported currently */
- scanstate->routine =3D &XmlTableRoutine;
+ /* Only XMLTABLE and JSON_TABLE are supported currently */
+ scanstate->routine =3D
+ tf->functype =3D=3D TFT_XMLTABLE ? &XmlTableRoutine : &JsonbTableRouti=
ne;
=20
scanstate->perTableCxt =3D
AllocSetContextCreate(CurrentMemoryContext,
@@ -381,14 +383,17 @@ tfuncInitialize(TableFuncScanState *tstate, ExprCon=
text *econtext, Datum doc)
routine->SetNamespace(tstate, ns_name, ns_uri);
}
=20
- /* Install the row filter expression into the table builder context */
- value =3D ExecEvalExpr(tstate->rowexpr, econtext, &isnull);
- if (isnull)
- ereport(ERROR,
- (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("row filter expression must not be null")));
+ if (routine->SetRowFilter)
+ {
+ /* Install the row filter expression into the table builder context */=
+ value =3D ExecEvalExpr(tstate->rowexpr, econtext, &isnull);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("row filter expression must not be null")));
=20
- routine->SetRowFilter(tstate, TextDatumGetCString(value));
+ routine->SetRowFilter(tstate, TextDatumGetCString(value));
+ }
=20
/*
* Install the column filter expressions into the table builder context=
=2E
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.=
c
index ba2eb0f894..5e35e57697 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1390,6 +1390,7 @@ _copyTableFunc(const TableFunc *from)
{
TableFunc *newnode =3D makeNode(TableFunc);
=20
+ COPY_SCALAR_FIELD(functype);
COPY_NODE_FIELD(ns_uris);
COPY_NODE_FIELD(ns_names);
COPY_NODE_FIELD(docexpr);
@@ -1400,7 +1401,9 @@ _copyTableFunc(const TableFunc *from)
COPY_NODE_FIELD(colcollations);
COPY_NODE_FIELD(colexprs);
COPY_NODE_FIELD(coldefexprs);
+ COPY_NODE_FIELD(colvalexprs);
COPY_BITMAPSET_FIELD(notnulls);
+ COPY_NODE_FIELD(plan);
COPY_SCALAR_FIELD(ordinalitycol);
COPY_LOCATION_FIELD(location);
=20
@@ -2662,6 +2665,76 @@ _copyJsonArgument(const JsonArgument *from)
return newnode;
}
=20
+/*
+ * _copyJsonTable
+ */
+static JsonTable *
+_copyJsonTable(const JsonTable *from)
+{
+ JsonTable *newnode =3D makeNode(JsonTable);
+
+ COPY_NODE_FIELD(common);
+ COPY_NODE_FIELD(columns);
+ COPY_NODE_FIELD(on_error);
+ COPY_NODE_FIELD(alias);
+ COPY_SCALAR_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonTableColumn
+ */
+static JsonTableColumn *
+_copyJsonTableColumn(const JsonTableColumn *from)
+{
+ JsonTableColumn *newnode =3D makeNode(JsonTableColumn);
+
+ COPY_SCALAR_FIELD(coltype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(typeName);
+ COPY_STRING_FIELD(pathspec);
+ COPY_SCALAR_FIELD(format);
+ COPY_SCALAR_FIELD(wrapper);
+ COPY_SCALAR_FIELD(omit_quotes);
+ COPY_NODE_FIELD(columns);
+ COPY_NODE_FIELD(on_empty);
+ COPY_NODE_FIELD(on_error);
+ COPY_SCALAR_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonTableParentNode
+ */
+static JsonTableParentNode *
+_copyJsonTableParentNode(const JsonTableParentNode *from)
+{
+ JsonTableParentNode *newnode =3D makeNode(JsonTableParentNode);
+
+ COPY_NODE_FIELD(path);
+ COPY_NODE_FIELD(child);
+ COPY_SCALAR_FIELD(colMin);
+ COPY_SCALAR_FIELD(colMax);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonTableSiblingNode
+ */
+static JsonTableSiblingNode *
+_copyJsonTableSiblingNode(const JsonTableSiblingNode *from)
+{
+ JsonTableSiblingNode *newnode =3D makeNode(JsonTableSiblingNode);
+
+ COPY_NODE_FIELD(larg);
+ COPY_NODE_FIELD(rarg);
+
+ return newnode;
+}
+
/* ****************************************************************
* pathnodes.h copy functions
*
@@ -5739,6 +5812,18 @@ copyObjectImpl(const void *from)
case T_JsonItemCoercions:
retval =3D _copyJsonItemCoercions(from);
break;
+ case T_JsonTable:
+ retval =3D _copyJsonTable(from);
+ break;
+ case T_JsonTableColumn:
+ retval =3D _copyJsonTableColumn(from);
+ break;
+ case T_JsonTableParentNode:
+ retval =3D _copyJsonTableParentNode(from);
+ break;
+ case T_JsonTableSiblingNode:
+ retval =3D _copyJsonTableSiblingNode(from);
+ break;
=20
/*
* RELATION NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfunc=
s.c
index ffb186825f..adcb608756 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -127,6 +127,7 @@ _equalRangeVar(const RangeVar *a, const RangeVar *b)
static bool
_equalTableFunc(const TableFunc *a, const TableFunc *b)
{
+ COMPARE_SCALAR_FIELD(functype);
COMPARE_NODE_FIELD(ns_uris);
COMPARE_NODE_FIELD(ns_names);
COMPARE_NODE_FIELD(docexpr);
@@ -137,13 +138,35 @@ _equalTableFunc(const TableFunc *a, const TableFunc=
*b)
COMPARE_NODE_FIELD(colcollations);
COMPARE_NODE_FIELD(colexprs);
COMPARE_NODE_FIELD(coldefexprs);
+ COMPARE_NODE_FIELD(colvalexprs);
COMPARE_BITMAPSET_FIELD(notnulls);
+ COMPARE_NODE_FIELD(plan);
COMPARE_SCALAR_FIELD(ordinalitycol);
COMPARE_LOCATION_FIELD(location);
=20
return true;
}
=20
+static bool
+_equalJsonTableParentNode(const JsonTableParentNode *a, const JsonTableP=
arentNode *b)
+{
+ COMPARE_NODE_FIELD(path);
+ COMPARE_NODE_FIELD(child);
+ COMPARE_SCALAR_FIELD(colMin);
+ COMPARE_SCALAR_FIELD(colMax);
+
+ return true;
+}
+
+static bool
+_equalJsonTableSiblingNode(const JsonTableSiblingNode *a, const JsonTabl=
eSiblingNode *b)
+{
+ COMPARE_NODE_FIELD(larg);
+ COMPARE_NODE_FIELD(rarg);
+
+ return true;
+}
+
static bool
_equalIntoClause(const IntoClause *a, const IntoClause *b)
{
@@ -3595,6 +3618,12 @@ equal(const void *a, const void *b)
case T_JsonItemCoercions:
retval =3D _equalJsonItemCoercions(a, b);
break;
+ case T_JsonTableParentNode:
+ retval =3D _equalJsonTableParentNode(a, b);
+ break;
+ case T_JsonTableSiblingNode:
+ retval =3D _equalJsonTableSiblingNode(a, b);
+ break;
=20
/*
* RELATION NODES
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.=
c
index 97bd0bac6b..eb739f2ee0 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2434,6 +2434,8 @@ expression_tree_walker(Node *node,
return true;
if (walker(tf->coldefexprs, context))
return true;
+ if (walker(tf->colvalexprs, context))
+ return true;
}
break;
case T_JsonValueExpr:
@@ -3445,6 +3447,7 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->rowexpr, tf->rowexpr, Node *);
MUTATE(newnode->colexprs, tf->colexprs, List *);
MUTATE(newnode->coldefexprs, tf->coldefexprs, List *);
+ MUTATE(newnode->colvalexprs, tf->colvalexprs, List *);
return (Node *) newnode;
}
break;
@@ -4401,6 +4404,30 @@ raw_expression_tree_walker(Node *node,
return true;
}
break;
+ case T_JsonTable:
+ {
+ JsonTable *jt =3D (JsonTable *) node;
+
+ if (walker(jt->common, context))
+ return true;
+ if (walker(jt->columns, context))
+ return true;
+ }
+ break;
+ case T_JsonTableColumn:
+ {
+ JsonTableColumn *jtc =3D (JsonTableColumn *) node;
+
+ if (walker(jtc->typeName, context))
+ return true;
+ if (walker(jtc->on_empty, context))
+ return true;
+ if (walker(jtc->on_error, context))
+ return true;
+ if (jtc->coltype =3D=3D JTC_NESTED && walker(jtc->columns, context))=
+ return true;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8012904870..0ec2b6b831 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1088,6 +1088,7 @@ _outTableFunc(StringInfo str, const TableFunc *node=
)
{
WRITE_NODE_TYPE("TABLEFUNC");
=20
+ WRITE_ENUM_FIELD(functype, TableFuncType);
WRITE_NODE_FIELD(ns_uris);
WRITE_NODE_FIELD(ns_names);
WRITE_NODE_FIELD(docexpr);
@@ -1098,7 +1099,9 @@ _outTableFunc(StringInfo str, const TableFunc *node=
)
WRITE_NODE_FIELD(colcollations);
WRITE_NODE_FIELD(colexprs);
WRITE_NODE_FIELD(coldefexprs);
+ WRITE_NODE_FIELD(colvalexprs);
WRITE_BITMAPSET_FIELD(notnulls);
+ WRITE_NODE_FIELD(plan);
WRITE_INT_FIELD(ordinalitycol);
WRITE_LOCATION_FIELD(location);
}
@@ -1862,6 +1865,26 @@ _outJsonItemCoercions(StringInfo str, const JsonIt=
emCoercions *node)
WRITE_NODE_FIELD(composite);
}
=20
+static void
+_outJsonTableParentNode(StringInfo str, const JsonTableParentNode *node)=
+{
+ WRITE_NODE_TYPE("JSONTABPNODE");
+
+ WRITE_NODE_FIELD(path);
+ WRITE_NODE_FIELD(child);
+ WRITE_INT_FIELD(colMin);
+ WRITE_INT_FIELD(colMax);
+}
+
+static void
+_outJsonTableSiblingNode(StringInfo str, const JsonTableSiblingNode *nod=
e)
+{
+ WRITE_NODE_TYPE("JSONTABSNODE");
+
+ WRITE_NODE_FIELD(larg);
+ WRITE_NODE_FIELD(rarg);
+}
+
/***********************************************************************=
******
*
* Stuff from pathnodes.h.
@@ -4662,6 +4685,12 @@ outNode(StringInfo str, const void *obj)
case T_JsonItemCoercions:
_outJsonItemCoercions(str, obj);
break;
+ case T_JsonTableParentNode:
+ _outJsonTableParentNode(str, obj);
+ break;
+ case T_JsonTableSiblingNode:
+ _outJsonTableSiblingNode(str, obj);
+ break;
=20
default:
=20
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.=
c
index 2eee045db0..833f6627cf 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -535,6 +535,7 @@ _readTableFunc(void)
{
READ_LOCALS(TableFunc);
=20
+ READ_ENUM_FIELD(functype, TableFuncType);
READ_NODE_FIELD(ns_uris);
READ_NODE_FIELD(ns_names);
READ_NODE_FIELD(docexpr);
@@ -545,7 +546,9 @@ _readTableFunc(void)
READ_NODE_FIELD(colcollations);
READ_NODE_FIELD(colexprs);
READ_NODE_FIELD(coldefexprs);
+ READ_NODE_FIELD(colvalexprs);
READ_BITMAPSET_FIELD(notnulls);
+ READ_NODE_FIELD(plan);
READ_INT_FIELD(ordinalitycol);
READ_LOCATION_FIELD(location);
=20
@@ -1496,6 +1499,30 @@ _readJsonExpr(void)
READ_DONE();
}
=20
+static JsonTableParentNode *
+_readJsonTableParentNode(void)
+{
+ READ_LOCALS(JsonTableParentNode);
+
+ READ_NODE_FIELD(path);
+ READ_NODE_FIELD(child);
+ READ_INT_FIELD(colMin);
+ READ_INT_FIELD(colMax);
+
+ READ_DONE();
+}
+
+static JsonTableSiblingNode *
+_readJsonTableSiblingNode(void)
+{
+ READ_LOCALS(JsonTableSiblingNode);
+
+ READ_NODE_FIELD(larg);
+ READ_NODE_FIELD(rarg);
+
+ READ_DONE();
+}
+
/*
* _readJsonCoercion
*/
@@ -3150,6 +3177,10 @@ parseNodeString(void)
return_value =3D _readJsonCoercion();
else if (MATCH("JSONITEMCOERCIONS", 17))
return_value =3D _readJsonItemCoercions();
+ else if (MATCH("JSONTABPNODE", 12))
+ return_value =3D _readJsonTableParentNode();
+ else if (MATCH("JSONTABSNODE", 12))
+ return_value =3D _readJsonTableSiblingNode();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile
index 5ddb9a92f0..56dafdd961 100644
--- a/src/backend/parser/Makefile
+++ b/src/backend/parser/Makefile
@@ -23,6 +23,7 @@ OBJS =3D \
parse_enr.o \
parse_expr.o \
parse_func.o \
+ parse_jsontable.o \
parse_node.o \
parse_oper.o \
parse_param.o \
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b70949b122..01804dab46 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -645,15 +645,25 @@ static Node *makeRecursiveViewSelect(char *relname,=
List *aliases, Node *query);
json_object_aggregate_constructor
json_array_aggregate_constructor
json_path_specification
+ json_table
+ json_table_column_definition
+ json_table_ordinality_column_definition
+ json_table_regular_column_definition
+ json_table_formatted_column_definition
+ json_table_exists_column_definition
+ json_table_nested_columns
=20
%type <list> json_name_and_value_list
json_value_expr_list
json_array_aggregate_order_by_clause_opt
json_arguments
json_passing_clause_opt
+ json_table_columns_clause
+ json_table_column_definition_list
=20
%type <str> json_table_path_name
json_as_path_name_clause_opt
+ json_table_column_path_specification_clause_opt
=20
%type <ival> json_encoding
json_encoding_clause_opt
@@ -667,6 +677,7 @@ static Node *makeRecursiveViewSelect(char *relname, L=
ist *aliases, Node *query);
json_behavior_true
json_behavior_false
json_behavior_unknown
+ json_behavior_empty
json_behavior_empty_array
json_behavior_empty_object
json_behavior_default
@@ -674,6 +685,8 @@ static Node *makeRecursiveViewSelect(char *relname, L=
ist *aliases, Node *query);
json_query_behavior
json_exists_error_behavior
json_exists_error_clause_opt
+ json_table_error_behavior
+ json_table_error_clause_opt
=20
%type <on_behavior> json_value_on_behavior_clause_opt
json_query_on_behavior_clause_opt
@@ -748,7 +761,7 @@ static Node *makeRecursiveViewSelect(char *relname, L=
ist *aliases, Node *query);
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
=20
JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTA=
GG
- JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_VALUE
+ JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_TABLE JSON_VALUE
=20
KEY KEYS KEEP
=20
@@ -758,8 +771,8 @@ static Node *makeRecursiveViewSelect(char *relname, L=
ist *aliases, Node *query);
=20
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH=
_P MOVE
=20
- NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE
- NORMALIZE NORMALIZED
+ NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NFC NFD NFKC NFKD N=
O
+ NONE NORMALIZE NORMALIZED
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
NULLS_P NUMERIC
=20
@@ -767,7 +780,7 @@ static Node *makeRecursiveViewSelect(char *relname, L=
ist *aliases, Node *query);
ORDER ORDINALITY OTHERS OUT_P OUTER_P
OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
=20
- PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY=
+ PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLANS P=
OLICY
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
=20
@@ -869,7 +882,7 @@ static Node *makeRecursiveViewSelect(char *relname, L=
ist *aliases, Node *query);
*/
%nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */
%nonassoc ERROR_P EMPTY_P DEFAULT ABSENT /* JSON error/empty behavior */=
-%nonassoc FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN UNIQUE JSON
+%nonassoc FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN UNIQUE JSON COLUMNS
%nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROL=
LUP
%left Op OPERATOR /* multi-character ops and user-defined operators */=
%left '+' '-'
@@ -894,6 +907,10 @@ static Node *makeRecursiveViewSelect(char *relname, =
List *aliases, Node *query);
*/
%left JOIN CROSS LEFT FULL RIGHT INNER_P NATURAL
=20
+%nonassoc json_table_column
+%nonassoc NESTED
+%left PATH
+
%nonassoc empty_json_unique
%left WITHOUT WITH_LA_UNIQUE
=20
@@ -12290,6 +12307,19 @@ table_ref: relation_expr opt_alias_clause
$2->alias =3D $4;
$$ =3D (Node *) $2;
}
+ | json_table opt_alias_clause
+ {
+ JsonTable *jt =3D castNode(JsonTable, $1);
+ jt->alias =3D $2;
+ $$ =3D (Node *) jt;
+ }
+ | LATERAL_P json_table opt_alias_clause
+ {
+ JsonTable *jt =3D castNode(JsonTable, $2);
+ jt->alias =3D $3;
+ jt->lateral =3D true;
+ $$ =3D (Node *) jt;
+ }
;
=20
=20
@@ -12834,6 +12864,8 @@ xmltable_column_option_el:
{ $$ =3D makeDefElem("is_not_null", (Node *) makeInteger(true), @1);=
}
| NULL_P
{ $$ =3D makeDefElem("is_not_null", (Node *) makeInteger(false), @1)=
; }
+ | PATH b_expr
+ { $$ =3D makeDefElem("path", $2, @1); }
;
=20
xml_namespace_list:
@@ -15361,6 +15393,10 @@ json_behavior_unknown:
UNKNOWN { $$ =3D makeJsonBehavior(JSON_BEHAVIOR_UNKNOWN, NULL); }
;
=20
+json_behavior_empty:
+ EMPTY_P { $$ =3D makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL);=
}
+ ;
+
json_behavior_empty_array:
EMPTY_P ARRAY { $$ =3D makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NU=
LL); }
/* non-standard, for Oracle compatibility only */
@@ -15475,6 +15511,153 @@ json_query_on_behavior_clause_opt:
{ $$.on_empty =3D NULL; $$.on_error =3D NULL; }
;
=20
+json_table:
+ JSON_TABLE '('
+ json_api_common_syntax
+ json_table_columns_clause
+ json_table_error_clause_opt
+ ')'
+ {
+ JsonTable *n =3D makeNode(JsonTable);
+ n->common =3D (JsonCommon *) $3;
+ n->columns =3D $4;
+ n->on_error =3D $5;
+ n->location =3D @1;
+ $$ =3D (Node *) n;
+ }
+ ;
+
+json_table_columns_clause:
+ COLUMNS '(' json_table_column_definition_list ')' { $$ =3D $3; }
+ ;
+
+json_table_column_definition_list:
+ json_table_column_definition
+ { $$ =3D list_make1($1); }
+ | json_table_column_definition_list ',' json_table_column_definition
+ { $$ =3D lappend($1, $3); }
+ ;
+
+json_table_column_definition:
+ json_table_ordinality_column_definition %prec json_table_column
+ | json_table_regular_column_definition %prec json_table_column
+ | json_table_formatted_column_definition %prec json_table_column
+ | json_table_exists_column_definition %prec json_table_column
+ | json_table_nested_columns
+ ;
+
+json_table_ordinality_column_definition:
+ ColId FOR ORDINALITY
+ {
+ JsonTableColumn *n =3D makeNode(JsonTableColumn);
+ n->coltype =3D JTC_FOR_ORDINALITY;
+ n->name =3D $1;
+ n->location =3D @1;
+ $$ =3D (Node *) n;
+ }
+ ;
+
+json_table_regular_column_definition:
+ ColId Typename
+ json_table_column_path_specification_clause_opt
+ json_wrapper_clause_opt
+ json_quotes_clause_opt
+ json_value_on_behavior_clause_opt
+ {
+ JsonTableColumn *n =3D makeNode(JsonTableColumn);
+ n->coltype =3D JTC_REGULAR;
+ n->name =3D $1;
+ n->typeName =3D $2;
+ n->format =3D makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1)=
;
+ n->wrapper =3D $4; /* JSW_NONE */
+ n->omit_quotes =3D $5; /* false */
+ n->pathspec =3D $3;
+ n->on_empty =3D $6.on_empty;
+ n->on_error =3D $6.on_error;
+ n->location =3D @1;
+ $$ =3D (Node *) n;
+ }
+ ;
+
+json_table_exists_column_definition:
+ ColId Typename
+ EXISTS json_table_column_path_specification_clause_opt
+ json_exists_error_clause_opt
+ {
+ JsonTableColumn *n =3D makeNode(JsonTableColumn);
+ n->coltype =3D JTC_EXISTS;
+ n->name =3D $1;
+ n->typeName =3D $2;
+ n->format =3D makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1)=
;
+ n->wrapper =3D JSW_NONE;
+ n->omit_quotes =3D false;
+ n->pathspec =3D $4;
+ n->on_empty =3D NULL;
+ n->on_error =3D $5;
+ n->location =3D @1;
+ $$ =3D (Node *) n;
+ }
+ ;
+
+json_table_error_behavior:
+ json_behavior_error
+ | json_behavior_empty
+ ;
+
+json_table_error_clause_opt:
+ json_table_error_behavior ON ERROR_P { $$ =3D $1; }
+ | /* EMPTY */ { $$ =3D NULL; }
+ ;
+
+json_table_column_path_specification_clause_opt:
+ PATH Sconst { $$ =3D $2; }
+ | /* EMPTY */ %prec json_table_column { $$ =3D NULL; }
+ ;
+
+json_table_formatted_column_definition:
+ ColId Typename FORMAT json_representation
+ json_table_column_path_specification_clause_opt
+ json_wrapper_clause_opt
+ json_quotes_clause_opt
+ json_query_on_behavior_clause_opt
+ {
+ JsonTableColumn *n =3D makeNode(JsonTableColumn);
+ n->coltype =3D JTC_FORMATTED;
+ n->name =3D $1;
+ n->typeName =3D $2;
+ n->format =3D castNode(JsonFormat, $4);
+ n->pathspec =3D $5;
+ n->wrapper =3D $6;
+ if (n->wrapper !=3D JSW_NONE && $7 !=3D JS_QUOTES_UNSPEC)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("SQL/JSON QUOTES behavior shall not be specified when WI=
TH WRAPPER is used"),
+ parser_errposition(@7)));
+ n->omit_quotes =3D $7 =3D=3D JS_QUOTES_OMIT;
+ n->on_empty =3D $8.on_empty;
+ n->on_error =3D $8.on_error;
+ n->location =3D @1;
+ $$ =3D (Node *) n;
+ }
+ ;
+
+json_table_nested_columns:
+ NESTED path_opt Sconst json_table_columns_clause
+ {
+ JsonTableColumn *n =3D makeNode(JsonTableColumn);
+ n->coltype =3D JTC_NESTED;
+ n->pathspec =3D $3;
+ n->columns =3D $4;
+ n->location =3D @1;
+ $$ =3D (Node *) n;
+ }
+ ;
+
+path_opt:
+ PATH { }
+ | /* EMPTY */ { }
+ ;
+
json_returning_clause_opt:
RETURNING Typename
{
@@ -16339,6 +16522,7 @@ unreserved_keyword:
| MOVE
| NAME_P
| NAMES
+ | NESTED
| NEW
| NEXT
| NFC
@@ -16372,6 +16556,7 @@ unreserved_keyword:
| PARTITION
| PASSING
| PASSWORD
+ | PATH
| PLANS
| POLICY
| PRECEDING
@@ -16535,6 +16720,7 @@ col_name_keyword:
| JSON_QUERY
| JSON_SCALAR
| JSON_SERIALIZE
+ | JSON_TABLE
| JSON_VALUE
| LEAST
| NATIONAL
@@ -16902,6 +17088,7 @@ bare_label_keyword:
| JSON_QUERY
| JSON_SCALAR
| JSON_SERIALIZE
+ | JSON_TABLE
| JSON_VALUE
| KEEP
| KEY
@@ -16939,6 +17126,7 @@ bare_label_keyword:
| NATIONAL
| NATURAL
| NCHAR
+ | NESTED
| NEW
| NEXT
| NFC
@@ -16982,6 +17170,7 @@ bare_label_keyword:
| PARTITION
| PASSING
| PASSWORD
+ | PATH
| PLACING
| PLANS
| POLICY
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse=
_clause.c
index 078029ba1f..cea66ec5f8 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -696,7 +696,7 @@ transformRangeTableFunc(ParseState *pstate, RangeTabl=
eFunc *rtf)
char **names;
int colno;
=20
- /* Currently only XMLTABLE is supported */
+ tf->functype =3D TFT_XMLTABLE;
constructName =3D "XMLTABLE";
docType =3D XMLOID;
=20
@@ -1100,13 +1100,17 @@ transformFromClauseItem(ParseState *pstate, Node =
*n,
rtr->rtindex =3D nsitem->p_rtindex;
return (Node *) rtr;
}
- else if (IsA(n, RangeTableFunc))
+ else if (IsA(n, RangeTableFunc) || IsA(n, JsonTable))
{
/* table function is like a plain relation */
RangeTblRef *rtr;
ParseNamespaceItem *nsitem;
=20
- nsitem =3D transformRangeTableFunc(pstate, (RangeTableFunc *) n);
+ if (IsA(n, RangeTableFunc))
+ nsitem =3D transformRangeTableFunc(pstate, (RangeTableFunc *) n);
+ else
+ nsitem =3D transformJsonTable(pstate, (JsonTable *) n);
+
*top_nsitem =3D nsitem;
*namespace =3D list_make1(nsitem);
rtr =3D makeNode(RangeTblRef);
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_e=
xpr.c
index 678840593b..579c68a929 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4092,7 +4092,7 @@ transformJsonExprCommon(ParseState *pstate, JsonFun=
cExpr *func)
Node *pathspec;
JsonFormatType format;
=20
- if (func->common->pathname)
+ if (func->common->pathname && func->op !=3D IS_JSON_TABLE)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("JSON_TABLE path name is not allowed here"),
@@ -4130,14 +4130,13 @@ transformJsonExprCommon(ParseState *pstate, JsonF=
uncExpr *func)
transformJsonPassingArgs(pstate, format, func->common->passing,
&jsexpr->passing_values, &jsexpr->passing_names);
=20
- if (func->op !=3D IS_JSON_EXISTS)
+ if (func->op !=3D IS_JSON_EXISTS && func->op !=3D IS_JSON_TABLE)
jsexpr->on_empty =3D transformJsonBehavior(pstate, func->on_empty,
JSON_BEHAVIOR_NULL);
=20
jsexpr->on_error =3D transformJsonBehavior(pstate, func->on_error,
- func->op =3D=3D IS_JSON_EXISTS ?
- JSON_BEHAVIOR_FALSE :
- JSON_BEHAVIOR_NULL);
+ func->op =3D=3D IS_JSON_EXISTS ? JSON_BEHAVIOR_FALSE :
+ func->op =3D=3D IS_JSON_TABLE ? JSON_BEHAVIOR_EMPTY : JSON_BEHAVIOR_NU=
LL);
=20
return jsexpr;
}
@@ -4438,6 +4437,20 @@ transformJsonFuncExpr(ParseState *pstate, JsonFunc=
Expr *func)
jsexpr->result_coercion->expr =3D NULL;
}
break;
+
+ case IS_JSON_TABLE:
+ jsexpr->returning =3D makeNode(JsonReturning);
+ jsexpr->returning->format =3D makeJsonFormat(JS_FORMAT_DEFAULT, JS_EN=
C_DEFAULT, -1);
+ jsexpr->returning->typid =3D exprType(contextItemExpr);
+ jsexpr->returning->typmod =3D -1;
+
+ if (jsexpr->returning->typid !=3D JSONBOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("JSON_TABLE() is not yet implemented for json type"),
+ parser_errposition(pstate, func->location)));
+
+ break;
}
=20
if (exprType(contextItemExpr) !=3D JSONBOID)
diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/pa=
rse_jsontable.c
new file mode 100644
index 0000000000..94c96606c1
--- /dev/null
+++ b/src/backend/parser/parse_jsontable.c
@@ -0,0 +1,467 @@
+/*----------------------------------------------------------------------=
---
+ *
+ * parse_jsontable.c
+ * pasring of JSON_TABLE
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group=
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/parser/parse_jsontable.c
+ *
+ *----------------------------------------------------------------------=
---
+ */
+
+#include "postgres.h"
+
+#include "miscadmin.h"
+
+#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_type.h"
+#include "utils/builtins.h"
+#include "utils/json.h"
+#include "utils/lsyscache.h"
+
+/* Context for JSON_TABLE transformation */
+typedef struct JsonTableContext
+{
+ ParseState *pstate; /* parsing state */
+ JsonTable *table; /* untransformed node */
+ TableFunc *tablefunc; /* transformed node */
+ List *pathNames; /* list of all path and columns names */
+ Oid contextItemTypid; /* type oid of context item (json/jsonb) */
+} JsonTableContext;
+
+static JsonTableParentNode * transformJsonTableColumns(JsonTableContext =
*cxt,
+ List *columns,
+ char *pathSpec,
+ int location);
+
+static Node *
+makeStringConst(char *str, int location)
+{
+ A_Const *n =3D makeNode(A_Const);
+
+ n->val.node.type =3D T_String;
+ n->val.sval.val =3D str;
+ n->location =3D location;
+
+ return (Node *)n;
+}
+
+/*
+ * Transform JSON_TABLE column
+ * - regular column into JSON_VALUE()
+ * - FORMAT JSON column into JSON_QUERY()
+ * - EXISTS column into JSON_EXISTS()
+ */
+static Node *
+transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
+ List *passingArgs, bool errorOnError)
+{
+ JsonFuncExpr *jfexpr =3D makeNode(JsonFuncExpr);
+ JsonCommon *common =3D makeNode(JsonCommon);
+ JsonOutput *output =3D makeNode(JsonOutput);
+ JsonPathSpec pathspec;
+ JsonFormat *default_format;
+
+ jfexpr->op =3D
+ jtc->coltype =3D=3D JTC_REGULAR ? IS_JSON_VALUE :
+ jtc->coltype =3D=3D JTC_EXISTS ? IS_JSON_EXISTS : IS_JSON_QUERY;
+ jfexpr->common =3D common;
+ jfexpr->output =3D output;
+ jfexpr->on_empty =3D jtc->on_empty;
+ jfexpr->on_error =3D jtc->on_error;
+ if (!jfexpr->on_error && errorOnError)
+ jfexpr->on_error =3D makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL);
+ jfexpr->omit_quotes =3D jtc->omit_quotes;
+ jfexpr->wrapper =3D jtc->wrapper;
+ jfexpr->location =3D jtc->location;
+
+ output->typeName =3D jtc->typeName;
+ output->returning =3D makeNode(JsonReturning);
+ output->returning->format =3D jtc->format;
+
+ default_format =3D makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1=
);
+
+ common->pathname =3D NULL;
+ common->expr =3D makeJsonValueExpr((Expr *) contextItemExpr, default_fo=
rmat);
+ common->passing =3D passingArgs;
+
+ if (jtc->pathspec)
+ pathspec =3D jtc->pathspec;
+ else
+ {
+ /* Construct default path as '$."column_name"' */
+ StringInfoData path;
+
+ initStringInfo(&path);
+
+ appendStringInfoString(&path, "$.");
+ escape_json(&path, jtc->name);
+
+ pathspec =3D path.data;
+ }
+
+ common->pathspec =3D makeStringConst(pathspec, -1);
+
+ return (Node *) jfexpr;
+}
+
+static bool
+isJsonTablePathNameDuplicate(JsonTableContext *cxt, const char *pathname=
)
+{
+ ListCell *lc;
+
+ foreach(lc, cxt->pathNames)
+ {
+ if (!strcmp(pathname, (const char *) lfirst(lc)))
+ return true;
+ }
+
+ return false;
+}
+
+/* Recursively register column name in the path name list. */
+static void
+registerJsonTableColumn(JsonTableContext *cxt, char *colname)
+{
+ if (isJsonTablePathNameDuplicate(cxt, colname))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_ALIAS),
+ errmsg("duplicate JSON_TABLE column name: %s", colname),
+ errhint("JSON_TABLE path names and column names shall be "
+ "distinct from one another")));
+
+ cxt->pathNames =3D lappend(cxt->pathNames, colname);
+}
+
+/* Recursively register all nested column names in the path name list. *=
/
+static void
+registerAllJsonTableColumns(JsonTableContext *cxt, List *columns)
+{
+ ListCell *lc;
+
+ foreach(lc, columns)
+ {
+ JsonTableColumn *jtc =3D castNode(JsonTableColumn, lfirst(lc));
+
+ if (jtc->coltype =3D=3D JTC_NESTED)
+ registerAllJsonTableColumns(cxt, jtc->columns);
+ else
+ registerJsonTableColumn(cxt, jtc->name);
+ }
+}
+
+static Node *
+transformNestedJsonTableColumn(JsonTableContext *cxt, JsonTableColumn *j=
tc)
+{
+ JsonTableParentNode *node;
+
+ node =3D transformJsonTableColumns(cxt, jtc->columns, jtc->pathspec,
+ jtc->location);
+
+ return (Node *) node;
+}
+
+static Node *
+makeJsonTableSiblingJoin(Node *lnode, Node *rnode)
+{
+ JsonTableSiblingNode *join =3D makeNode(JsonTableSiblingNode);
+
+ join->larg =3D lnode;
+ join->rarg =3D rnode;
+
+ return (Node *) join;
+}
+
+/*
+ * Recursively transform child (nested) JSON_TABLE columns.
+ *
+ * Child columns are transformed into a binary tree of union-joined
+ * JsonTableSiblingNodes.
+ */
+static Node *
+transformJsonTableChildColumns(JsonTableContext *cxt, List *columns)
+{
+ Node *res =3D NULL;
+ ListCell *lc;
+
+ /* transform all nested columns into union join */
+ foreach(lc, columns)
+ {
+ JsonTableColumn *jtc =3D castNode(JsonTableColumn, lfirst(lc));
+ Node *node;
+
+ if (jtc->coltype !=3D JTC_NESTED)
+ continue;
+
+ node =3D transformNestedJsonTableColumn(cxt, jtc);
+
+ /* join transformed node with previous sibling nodes */
+ res =3D res ? makeJsonTableSiblingJoin(res, node) : node;
+ }
+
+ return res;
+}
+
+/* Check whether type is json/jsonb, array, or record. */
+static bool
+typeIsComposite(Oid typid)
+{
+ if (typid =3D=3D JSONOID ||
+ typid =3D=3D JSONBOID ||
+ typid =3D=3D RECORDOID ||
+ type_is_array(typid))
+ return true;
+
+ switch (get_typtype(typid))
+ {
+ case TYPTYPE_COMPOSITE:
+ return true;
+
+ case TYPTYPE_DOMAIN:
+ return typeIsComposite(getBaseType(typid));
+ }
+
+ return false;
+}
+
+/* Append transformed non-nested JSON_TABLE columns to the TableFunc nod=
e */
+static void
+appendJsonTableColumns(JsonTableContext *cxt, List *columns)
+{
+ ListCell *col;
+ ParseState *pstate =3D cxt->pstate;
+ JsonTable *jt =3D cxt->table;
+ TableFunc *tf =3D cxt->tablefunc;
+ bool errorOnError =3D jt->on_error &&
+ jt->on_error->btype =3D=3D JSON_BEHAVIOR_ERROR;
+
+ foreach(col, columns)
+ {
+ JsonTableColumn *rawc =3D castNode(JsonTableColumn, lfirst(col));
+ Oid typid;
+ int32 typmod;
+ Node *colexpr;
+
+ if (rawc->name)
+ {
+ /* make sure column names are unique */
+ ListCell *colname;
+
+ foreach(colname, tf->colnames)
+ if (!strcmp((const char *) colname, rawc->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("column name \"%s\" is not unique",
+ rawc->name),
+ parser_errposition(pstate, rawc->location)));
+
+ tf->colnames =3D lappend(tf->colnames,
+ makeString(pstrdup(rawc->name)));
+ }
+
+ /*
+ * Determine the type and typmod for the new column. FOR
+ * ORDINALITY columns are INTEGER by standard; the others are
+ * user-specified.
+ */
+ switch (rawc->coltype)
+ {
+ case JTC_FOR_ORDINALITY:
+ colexpr =3D NULL;
+ typid =3D INT4OID;
+ typmod =3D -1;
+ break;
+
+ case JTC_REGULAR:
+ typenameTypeIdAndMod(pstate, rawc->typeName, &typid, &typmod);
+
+ /*
+ * Use implicit FORMAT JSON for composite types (arrays and
+ * records)
+ */
+ if (typeIsComposite(typid))
+ rawc->coltype =3D JTC_FORMATTED;
+ else if (rawc->wrapper !=3D JSW_NONE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot use WITH WRAPPER clause with scalar columns"),
+ parser_errposition(pstate, rawc->location)));
+ else if (rawc->omit_quotes)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot use OMIT QUOTES clause with scalar columns"),
+ parser_errposition(pstate, rawc->location)));
+
+ /* FALLTHROUGH */
+ case JTC_EXISTS:
+ case JTC_FORMATTED:
+ {
+ Node *je;
+ CaseTestExpr *param =3D makeNode(CaseTestExpr);
+
+ param->collation =3D InvalidOid;
+ param->typeId =3D cxt->contextItemTypid;
+ param->typeMod =3D -1;
+
+ je =3D transformJsonTableColumn(rawc, (Node *) param,
+ NIL, errorOnError);
+
+ colexpr =3D transformExpr(pstate, je, EXPR_KIND_FROM_FUNCTION);
+ assign_expr_collations(pstate, colexpr);
+
+ typid =3D exprType(colexpr);
+ typmod =3D exprTypmod(colexpr);
+ break;
+ }
+
+ case JTC_NESTED:
+ continue;
+
+ default:
+ elog(ERROR, "unknown JSON_TABLE column type: %d", rawc->coltype);
+ break;
+ }
+
+ tf->coltypes =3D lappend_oid(tf->coltypes, typid);
+ tf->coltypmods =3D lappend_int(tf->coltypmods, typmod);
+ tf->colcollations =3D lappend_oid(tf->colcollations,
+ type_is_collatable(typid)
+ ? DEFAULT_COLLATION_OID
+ : InvalidOid);
+ tf->colvalexprs =3D lappend(tf->colvalexprs, colexpr);
+ }
+}
+
+/*
+ * Create transformed JSON_TABLE parent plan node by appending all non-n=
ested
+ * columns to the TableFunc node and remembering their indices in the
+ * colvalexprs list.
+ */
+static JsonTableParentNode *
+makeParentJsonTableNode(JsonTableContext *cxt, char *pathSpec, List *col=
umns)
+{
+ JsonTableParentNode *node =3D makeNode(JsonTableParentNode);
+
+ node->path =3D makeConst(JSONPATHOID, -1, InvalidOid, -1,
+ DirectFunctionCall1(jsonpath_in,
+ CStringGetDatum(pathSpec)),
+ false, false);
+
+ /* save start of column range */
+ node->colMin =3D list_length(cxt->tablefunc->colvalexprs);
+
+ appendJsonTableColumns(cxt, columns);
+
+ /* save end of column range */
+ node->colMax =3D list_length(cxt->tablefunc->colvalexprs) - 1;
+
+ node->errorOnError =3D
+ cxt->table->on_error &&
+ cxt->table->on_error->btype =3D=3D JSON_BEHAVIOR_ERROR;
+
+ return node;
+}
+
+static JsonTableParentNode *
+transformJsonTableColumns(JsonTableContext *cxt, List *columns, char *pa=
thSpec,
+ int location)
+{
+ JsonTableParentNode *node;
+
+ /* transform only non-nested columns */
+ node =3D makeParentJsonTableNode(cxt, pathSpec, columns);
+
+ /* transform recursively nested columns */
+ node->child =3D transformJsonTableChildColumns(cxt, columns);
+
+ return node;
+}
+
+/*
+ * transformJsonTable -
+ * Transform a raw JsonTable into TableFunc.
+ *
+ * Transform the document-generating expression, the row-generating expr=
ession,
+ * the column-generating expressions, and the default value expressions.=
+ */
+ParseNamespaceItem *
+transformJsonTable(ParseState *pstate, JsonTable *jt)
+{
+ JsonTableContext cxt;
+ TableFunc *tf =3D makeNode(TableFunc);
+ JsonFuncExpr *jfe =3D makeNode(JsonFuncExpr);
+ JsonCommon *jscommon;
+ char *rootPath;
+ bool is_lateral;
+
+ cxt.pstate =3D pstate;
+ cxt.table =3D jt;
+ cxt.tablefunc =3D tf;
+ cxt.pathNames =3D NIL;
+
+ registerAllJsonTableColumns(&cxt, jt->columns);
+
+ jscommon =3D copyObject(jt->common);
+ jscommon->pathspec =3D makeStringConst(pstrdup("$"), -1);
+
+ jfe->op =3D IS_JSON_TABLE;
+ jfe->common =3D jscommon;
+ jfe->on_error =3D jt->on_error;
+ jfe->location =3D jt->common->location;
+
+ /*
+ * We make lateral_only names of this level visible, whether or not the=
+ * RangeTableFunc is explicitly marked LATERAL. This is needed for SQL=
+ * spec compliance and seems useful on convenience grounds for all
+ * functions in FROM.
+ *
+ * (LATERAL can't nest within a single pstate level, so we don't need
+ * save/restore logic here.)
+ */
+ Assert(!pstate->p_lateral_active);
+ pstate->p_lateral_active =3D true;
+
+ tf->functype =3D TFT_JSON_TABLE;
+ tf->docexpr =3D transformExpr(pstate, (Node *) jfe, EXPR_KIND_FROM_FUNC=
TION);
+
+ cxt.contextItemTypid =3D exprType(tf->docexpr);
+
+ if (!IsA(jt->common->pathspec, A_Const) ||
+ castNode(A_Const, jt->common->pathspec)->val.node.type !=3D T_String)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only string constants supported in JSON_TABLE path specific=
ation"),
+ parser_errposition(pstate,
+ exprLocation(jt->common->pathspec))));
+
+ rootPath =3D castNode(A_Const, jt->common->pathspec)->val.sval.val;
+
+ tf->plan =3D (Node *) transformJsonTableColumns(&cxt, jt->columns, root=
Path,
+ jt->common->location);
+
+ tf->ordinalitycol =3D -1; /* undefine ordinality column number */
+ tf->location =3D jt->location;
+
+ pstate->p_lateral_active =3D false;
+
+ /*
+ * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
+ * there are any lateral cross-references in it.
+ */
+ is_lateral =3D jt->lateral || contain_vars_of_level((Node *) tf, 0);
+
+ return addRangeTableEntryForTableFunc(pstate,
+ tf, jt->alias, is_lateral, true);
+}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/par=
se_relation.c
index c5c3f26ecf..decb2c2234 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1978,7 +1978,8 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
bool inFromCl)
{
RangeTblEntry *rte =3D makeNode(RangeTblEntry);
- char *refname =3D alias ? alias->aliasname : pstrdup("xmltable");
+ char *refname =3D alias ? alias->aliasname :
+ pstrdup(tf->functype =3D=3D TFT_XMLTABLE ? "xmltable" : "json_table");=
Alias *eref;
int numaliases;
=20
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse=
_target.c
index f390b29637..26143f973d 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1993,6 +1993,9 @@ FigureColnameInternal(Node *node, char **name)
case IS_JSON_EXISTS:
*name =3D "json_exists";
return 2;
+ case IS_JSON_TABLE:
+ *name =3D "json_table";
+ return 2;
}
break;
default:
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/ad=
t/jsonpath_exec.c
index 3b596be9fa..ebfa226bf3 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -61,9 +61,11 @@
=20
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "executor/execExpr.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
#include "regex/regex.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -74,6 +76,8 @@
#include "utils/guc.h"
#include "utils/json.h"
#include "utils/jsonpath.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
#include "utils/timestamp.h"
#include "utils/varlena.h"
=20
@@ -155,6 +159,57 @@ typedef struct JsonValueListIterator
ListCell *next;
} JsonValueListIterator;
=20
+/* Structures for JSON_TABLE execution */
+typedef struct JsonTableScanState JsonTableScanState;
+typedef struct JsonTableJoinState JsonTableJoinState;
+
+struct JsonTableScanState
+{
+ JsonTableScanState *parent;
+ JsonTableJoinState *nested;
+ MemoryContext mcxt;
+ JsonPath *path;
+ List *args;
+ JsonValueList found;
+ JsonValueListIterator iter;
+ Datum current;
+ int ordinal;
+ bool currentIsNull;
+ bool errorOnError;
+ bool advanceNested;
+ bool reset;
+};
+
+struct JsonTableJoinState
+{
+ union
+ {
+ struct
+ {
+ JsonTableJoinState *left;
+ JsonTableJoinState *right;
+ bool advanceRight;
+ } join;
+ JsonTableScanState scan;
+ } u;
+ bool is_join;
+};
+
+/* random number to identify JsonTableContext */
+#define JSON_TABLE_CONTEXT_MAGIC 418352867
+
+typedef struct JsonTableContext
+{
+ int magic;
+ struct
+ {
+ ExprState *expr;
+ JsonTableScanState *scan;
+ } *colexprs;
+ JsonTableScanState root;
+ bool empty;
+} JsonTableContext;
+
/* strict/lax flags is decomposed into four [un]wrap/error flags */
#define jspStrictAbsenseOfErrors(cxt) (!(cxt)->laxMode)
#define jspAutoUnwrap(cxt) ((cxt)->laxMode)
@@ -245,6 +300,7 @@ static JsonPathExecResult getArrayIndex(JsonPathExecC=
ontext *cxt,
JsonPathItem *jsp, JsonbValue *jb, int32 *index);
static JsonBaseObjectInfo setBaseObject(JsonPathExecContext *cxt,
JsonbValue *jbv, int32 id);
+static void JsonValueListClear(JsonValueList *jvl);
static void JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv);
static int JsonValueListLength(const JsonValueList *jvl);
static bool JsonValueListIsEmpty(JsonValueList *jvl);
@@ -262,6 +318,12 @@ static JsonbValue *wrapItemsInArray(const JsonValueL=
ist *items);
static int compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid=
2,
bool useTz, bool *have_error);
=20
+
+static JsonTableJoinState *JsonTableInitPlanState(JsonTableContext *cxt,=
+ Node *plan, JsonTableScanState *parent);
+static bool JsonTableNextRow(JsonTableScanState *scan);
+
+
/****************** User interface to JsonPath executor ****************=
****/
=20
/*
@@ -2458,6 +2520,13 @@ setBaseObject(JsonPathExecContext *cxt, JsonbValue=
*jbv, int32 id)
return baseObject;
}
=20
+static void
+JsonValueListClear(JsonValueList *jvl)
+{
+ jvl->singleton =3D NULL;
+ jvl->list =3D NULL;
+}
+
static void
JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv)
{
@@ -3067,3 +3136,370 @@ JsonItemFromDatum(Datum val, Oid typid, int32 typ=
mod, JsonbValue *res)
"casted to supported jsonpath types.")));
}
}
+
+/************************ JSON_TABLE functions *************************=
**/
+
+/*
+ * Returns private data from executor state. Ensure validity by check wi=
th
+ * MAGIC number.
+ */
+static inline JsonTableContext *
+GetJsonTableContext(TableFuncScanState *state, const char *fname)
+{
+ JsonTableContext *result;
+
+ if (!IsA(state, TableFuncScanState))
+ elog(ERROR, "%s called with invalid TableFuncScanState", fname);
+ result =3D (JsonTableContext *) state->opaque;
+ if (result->magic !=3D JSON_TABLE_CONTEXT_MAGIC)
+ elog(ERROR, "%s called with invalid TableFuncScanState", fname);
+
+ return result;
+}
+
+/* Recursively initialize JSON_TABLE scan state */
+static void
+JsonTableInitScanState(JsonTableContext *cxt, JsonTableScanState *scan,
+ JsonTableParentNode *node, JsonTableScanState *parent,
+ List *args, MemoryContext mcxt)
+{
+ int i;
+
+ scan->parent =3D parent;
+ scan->errorOnError =3D node->errorOnError;
+ scan->path =3D DatumGetJsonPathP(node->path->constvalue);
+ scan->args =3D args;
+ scan->mcxt =3D AllocSetContextCreate(mcxt, "JsonTableContext",
+ ALLOCSET_DEFAULT_SIZES);
+ scan->nested =3D node->child ?
+ JsonTableInitPlanState(cxt, node->child, scan) : NULL;
+ scan->current =3D PointerGetDatum(NULL);
+ scan->currentIsNull =3D true;
+
+ for (i =3D node->colMin; i <=3D node->colMax; i++)
+ cxt->colexprs[i].scan =3D scan;
+}
+
+/* Recursively initialize JSON_TABLE scan state */
+static JsonTableJoinState *
+JsonTableInitPlanState(JsonTableContext *cxt, Node *plan,
+ JsonTableScanState *parent)
+{
+ JsonTableJoinState *state =3D palloc0(sizeof(*state));
+
+ if (IsA(plan, JsonTableSiblingNode))
+ {
+ JsonTableSiblingNode *join =3D castNode(JsonTableSiblingNode, plan);
+
+ state->is_join =3D true;
+ state->u.join.left =3D JsonTableInitPlanState(cxt, join->larg, parent)=
;
+ state->u.join.right =3D JsonTableInitPlanState(cxt, join->rarg, parent=
);
+ }
+ else
+ {
+ JsonTableParentNode *node =3D castNode(JsonTableParentNode, plan);
+
+ state->is_join =3D false;
+
+ JsonTableInitScanState(cxt, &state->u.scan, node, parent,
+ parent->args, parent->mcxt);
+ }
+
+ return state;
+}
+
+/*
+ * JsonTableInitOpaque
+ * Fill in TableFuncScanState->opaque for JsonTable processor
+ */
+static void
+JsonTableInitOpaque(TableFuncScanState *state, int natts)
+{
+ JsonTableContext *cxt;
+ PlanState *ps =3D &state->ss.ps;
+ TableFuncScan *tfs =3D castNode(TableFuncScan, ps->plan);
+ TableFunc *tf =3D tfs->tablefunc;
+ JsonExpr *ci =3D castNode(JsonExpr, tf->docexpr);
+ JsonTableParentNode *root =3D castNode(JsonTableParentNode, tf->plan);
+ List *args =3D NIL;
+ ListCell *lc;
+ int i;
+
+ cxt =3D palloc0(sizeof(JsonTableContext));
+ cxt->magic =3D JSON_TABLE_CONTEXT_MAGIC;
+
+ if (ci->passing_values)
+ {
+ ListCell *exprlc;
+ ListCell *namelc;
+
+ forboth(exprlc, ci->passing_values,
+ namelc, ci->passing_names)
+ {
+ Expr *expr =3D (Expr *) lfirst(exprlc);
+ String *name =3D lfirst_node(String, namelc);
+ JsonPathVariableEvalContext *var =3D palloc(sizeof(*var));
+
+ var->name =3D pstrdup(name->val);
+ var->typid =3D exprType((Node *) expr);
+ var->typmod =3D exprTypmod((Node *) expr);
+ var->estate =3D ExecInitExpr(expr, ps);
+ var->econtext =3D ps->ps_ExprContext;
+ var->mcxt =3D CurrentMemoryContext;
+ var->evaluated =3D false;
+ var->value =3D (Datum) 0;
+ var->isnull =3D true;
+
+ args =3D lappend(args, var);
+ }
+ }
+
+ cxt->colexprs =3D palloc(sizeof(*cxt->colexprs) *
+ list_length(tf->colvalexprs));
+
+ JsonTableInitScanState(cxt, &cxt->root, root, NULL, args,
+ CurrentMemoryContext);
+
+ i =3D 0;
+
+ foreach(lc, tf->colvalexprs)
+ {
+ Expr *expr =3D lfirst(lc);
+
+ cxt->colexprs[i].expr =3D
+ ExecInitExprWithCaseValue(expr, ps,
+ &cxt->colexprs[i].scan->current,
+ &cxt->colexprs[i].scan->currentIsNull);
+
+ i++;
+ }
+
+ state->opaque =3D cxt;
+}
+
+/* Reset scan iterator to the beginning of the item list */
+static void
+JsonTableRescan(JsonTableScanState *scan)
+{
+ JsonValueListInitIterator(&scan->found, &scan->iter);
+ scan->current =3D PointerGetDatum(NULL);
+ scan->currentIsNull =3D true;
+ scan->advanceNested =3D false;
+ scan->ordinal =3D 0;
+}
+
+/* Reset context item of a scan, execute JSON path and reset a scan */
+static void
+JsonTableResetContextItem(JsonTableScanState *scan, Datum item)
+{
+ MemoryContext oldcxt;
+ JsonPathExecResult res;
+ Jsonb *js =3D (Jsonb *) DatumGetJsonbP(item);
+
+ JsonValueListClear(&scan->found);
+
+ MemoryContextResetOnly(scan->mcxt);
+
+ oldcxt =3D MemoryContextSwitchTo(scan->mcxt);
+
+ res =3D executeJsonPath(scan->path, scan->args, EvalJsonPathVar, js,
+ scan->errorOnError, &scan->found, false /* FIXME */);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ if (jperIsError(res))
+ {
+ Assert(!scan->errorOnError);
+ JsonValueListClear(&scan->found); /* EMPTY ON ERROR case */
+ }
+
+ JsonTableRescan(scan);
+}
+
+/*
+ * JsonTableSetDocument
+ * Install the input document
+ */
+static void
+JsonTableSetDocument(TableFuncScanState *state, Datum value)
+{
+ JsonTableContext *cxt =3D GetJsonTableContext(state, "JsonTableSetDocum=
ent");
+
+ JsonTableResetContextItem(&cxt->root, value);
+}
+
+/*
+ * Fetch next row from a union joined scan.
+ *
+ * Returned false at the end of a scan, true otherwise.
+ */
+static bool
+JsonTableNextJoinRow(JsonTableJoinState *state)
+{
+ if (!state->is_join)
+ return JsonTableNextRow(&state->u.scan);
+
+ if (!state->u.join.advanceRight)
+ {
+ /* fetch next outer row */
+ if (JsonTableNextJoinRow(state->u.join.left))
+ return true;
+
+ state->u.join.advanceRight =3D true; /* next inner row */
+ }
+
+ /* fetch next inner row */
+ return JsonTableNextJoinRow(state->u.join.right);
+}
+
+/* Recursively set 'reset' flag of scan and its child nodes */
+static void
+JsonTableJoinReset(JsonTableJoinState *state)
+{
+ if (state->is_join)
+ {
+ JsonTableJoinReset(state->u.join.left);
+ JsonTableJoinReset(state->u.join.right);
+ state->u.join.advanceRight =3D false;
+ }
+ else
+ {
+ state->u.scan.reset =3D true;
+ state->u.scan.advanceNested =3D false;
+
+ if (state->u.scan.nested)
+ JsonTableJoinReset(state->u.scan.nested);
+ }
+}
+
+/*
+ * Fetch next row from a simple scan with outer joined nested subscans.
+ *
+ * Returned false at the end of a scan, true otherwise.
+ */
+static bool
+JsonTableNextRow(JsonTableScanState *scan)
+{
+ JsonbValue *jbv;
+ MemoryContext oldcxt;
+
+ /* reset context item if requested */
+ if (scan->reset)
+ {
+ Assert(!scan->parent->currentIsNull);
+ JsonTableResetContextItem(scan, scan->parent->current);
+ scan->reset =3D false;
+ }
+
+ if (scan->advanceNested)
+ {
+ /* fetch next nested row */
+ if (JsonTableNextJoinRow(scan->nested))
+ return true;
+
+ scan->advanceNested =3D false;
+ }
+
+ /* fetch next row */
+ jbv =3D JsonValueListNext(&scan->found, &scan->iter);
+
+ if (!jbv)
+ {
+ scan->current =3D PointerGetDatum(NULL);
+ scan->currentIsNull =3D true;
+ return false; /* end of scan */
+ }
+
+ /* set current row item */
+ oldcxt =3D MemoryContextSwitchTo(scan->mcxt);
+ scan->current =3D JsonbPGetDatum(JsonbValueToJsonb(jbv));
+ scan->currentIsNull =3D false;
+ MemoryContextSwitchTo(oldcxt);
+
+ scan->ordinal++;
+
+ if (scan->nested)
+ {
+ JsonTableJoinReset(scan->nested);
+ scan->advanceNested =3D JsonTableNextJoinRow(scan->nested);
+ }
+
+ return true;
+}
+
+/*
+ * JsonTableFetchRow
+ * Prepare the next "current" tuple for upcoming GetValue calls.
+ * Returns FALSE if the row-filter expression returned no more rows.
+ */
+static bool
+JsonTableFetchRow(TableFuncScanState *state)
+{
+ JsonTableContext *cxt =3D GetJsonTableContext(state, "JsonTableFetchRow=
");
+
+ if (cxt->empty)
+ return false;
+
+ return JsonTableNextRow(&cxt->root);
+}
+
+/*
+ * JsonTableGetValue
+ * Return the value for column number 'colnum' for the current row.
+ *
+ * This leaks memory, so be sure to reset often the context in which it'=
s
+ * called.
+ */
+static Datum
+JsonTableGetValue(TableFuncScanState *state, int colnum,
+ Oid typid, int32 typmod, bool *isnull)
+{
+ JsonTableContext *cxt =3D GetJsonTableContext(state, "JsonTableGetValue=
");
+ ExprContext *econtext =3D state->ss.ps.ps_ExprContext;
+ ExprState *estate =3D cxt->colexprs[colnum].expr;
+ JsonTableScanState *scan =3D cxt->colexprs[colnum].scan;
+ Datum result;
+
+ if (scan->currentIsNull) /* NULL from outer/union join */
+ {
+ result =3D (Datum) 0;
+ *isnull =3D true;
+ }
+ else if (estate) /* regular column */
+ {
+ result =3D ExecEvalExpr(estate, econtext, isnull);
+ }
+ else
+ {
+ result =3D Int32GetDatum(scan->ordinal); /* ordinality column */
+ *isnull =3D false;
+ }
+
+ return result;
+}
+
+/*
+ * JsonTableDestroyOpaque
+ */
+static void
+JsonTableDestroyOpaque(TableFuncScanState *state)
+{
+ JsonTableContext *cxt =3D GetJsonTableContext(state, "JsonTableDestroyO=
paque");
+
+ /* not valid anymore */
+ cxt->magic =3D 0;
+
+ state->opaque =3D NULL;
+}
+
+const TableFuncRoutine JsonbTableRoutine =3D
+{
+ JsonTableInitOpaque,
+ JsonTableSetDocument,
+ NULL,
+ NULL,
+ NULL,
+ JsonTableFetchRow,
+ JsonTableGetValue,
+ JsonTableDestroyOpaque
+};
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ru=
leutils.c
index 439f48feb4..58d0fe4da0 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -495,6 +495,8 @@ static char *flatten_reloptions(Oid relid);
static void get_reloptions(StringInfo buf, Datum reloptions);
static void get_json_path_spec(Node *path_spec, deparse_context *context=
,
bool showimplicit);
+static void get_json_table_columns(TableFunc *tf, JsonTableParentNode *n=
ode,
+ deparse_context *context, bool showimplicit);
=20
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
=20
@@ -8378,7 +8380,8 @@ get_json_behavior(JsonBehavior *behavior, deparse_c=
ontext *context,
/*
* get_json_expr_options
*
- * Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS.
+ * Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS and=
+ * JSON_TABLE columns.
*/
static void
get_json_expr_options(JsonExpr *jsexpr, deparse_context *context,
@@ -9641,6 +9644,9 @@ get_rule_expr(Node *node, deparse_context *context,=
case IS_JSON_EXISTS:
appendStringInfoString(buf, "JSON_EXISTS(");
break;
+ default:
+ elog(ERROR, "unexpected JsonExpr type: %d", jexpr->op);
+ break;
}
=20
get_rule_expr(jexpr->formatted_expr, context, showimplicit);
@@ -10889,16 +10895,14 @@ get_sublink_expr(SubLink *sublink, deparse_cont=
ext *context)
=20
=20
/* ----------
- * get_tablefunc - Parse back a table function
+ * get_xmltable - Parse back a XMLTABLE function
* ----------
*/
static void
-get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit=
)
+get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit)=
{
StringInfo buf =3D context->buf;
=20
- /* XMLTABLE is the only existing implementation. */
-
appendStringInfoString(buf, "XMLTABLE(");
=20
if (tf->ns_uris !=3D NIL)
@@ -10989,6 +10993,219 @@ get_tablefunc(TableFunc *tf, deparse_context *c=
ontext, bool showimplicit)
appendStringInfoChar(buf, ')');
}
=20
+/*
+ * get_json_nested_columns - Parse back nested JSON_TABLE columns
+ */
+static void
+get_json_table_nested_columns(TableFunc *tf, Node *node,
+ deparse_context *context, bool showimplicit,
+ bool needcomma)
+{
+ if (IsA(node, JsonTableSiblingNode))
+ {
+ JsonTableSiblingNode *n =3D (JsonTableSiblingNode *) node;
+
+ get_json_table_nested_columns(tf, n->larg, context, showimplicit,
+ needcomma);
+ get_json_table_nested_columns(tf, n->rarg, context, showimplicit, true=
);
+ }
+ else
+ {
+ JsonTableParentNode *n =3D castNode(JsonTableParentNode, node);
+
+ if (needcomma)
+ appendStringInfoChar(context->buf, ',');
+
+ appendStringInfoChar(context->buf, ' ');
+ appendContextKeyword(context, "NESTED PATH ", 0, 0, 0);
+ get_const_expr(n->path, context, -1);
+ get_json_table_columns(tf, n, context, showimplicit);
+ }
+}
+
+/*
+ * get_json_table_columns - Parse back JSON_TABLE columns
+ */
+static void
+get_json_table_columns(TableFunc *tf, JsonTableParentNode *node,
+ deparse_context *context, bool showimplicit)
+{
+ StringInfo buf =3D context->buf;
+ JsonExpr *jexpr =3D castNode(JsonExpr, tf->docexpr);
+ ListCell *lc_colname;
+ ListCell *lc_coltype;
+ ListCell *lc_coltypmod;
+ ListCell *lc_colvarexpr;
+ int colnum =3D 0;
+
+ appendStringInfoChar(buf, ' ');
+ appendContextKeyword(context, "COLUMNS (", 0, 0, 0);
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel +=3D PRETTYINDENT_VAR;
+
+ forfour(lc_colname, tf->colnames,
+ lc_coltype, tf->coltypes,
+ lc_coltypmod, tf->coltypmods,
+ lc_colvarexpr, tf->colvalexprs)
+ {
+ char *colname =3D strVal(lfirst(lc_colname));
+ JsonExpr *colexpr;
+ Oid typid;
+ int32 typmod;
+ bool ordinality;
+ JsonBehaviorType default_behavior;
+
+ typid =3D lfirst_oid(lc_coltype);
+ typmod =3D lfirst_int(lc_coltypmod);
+ colexpr =3D castNode(JsonExpr, lfirst(lc_colvarexpr));
+
+ if (colnum < node->colMin)
+ {
+ colnum++;
+ continue;
+ }
+
+ if (colnum > node->colMax)
+ break;
+
+ if (colnum > node->colMin)
+ appendStringInfoString(buf, ", ");
+
+ colnum++;
+
+ ordinality =3D !colexpr;
+
+ appendContextKeyword(context, "", 0, 0, 0);
+
+ appendStringInfo(buf, "%s %s", quote_identifier(colname),
+ ordinality ? "FOR ORDINALITY" :
+ format_type_with_typemod(typid, typmod));
+ if (ordinality)
+ continue;
+
+ if (colexpr->op =3D=3D IS_JSON_EXISTS)
+ {
+ appendStringInfoString(buf, " EXISTS");
+ default_behavior =3D JSON_BEHAVIOR_FALSE;
+ }
+ else
+ {
+ if (colexpr->op =3D=3D IS_JSON_QUERY)
+ {
+ char typcategory;
+ bool typispreferred;
+
+ get_type_category_preferred(typid, &typcategory, &typispreferred);
+
+ if (typcategory =3D=3D TYPCATEGORY_STRING)
+ appendStringInfoString(buf,
+ colexpr->format->format =3D=3D JS_FORMAT_JSONB ?
+ " FORMAT JSONB" : " FORMAT JSON");
+ }
+
+ default_behavior =3D JSON_BEHAVIOR_NULL;
+ }
+
+ if (jexpr->on_error->btype =3D=3D JSON_BEHAVIOR_ERROR)
+ default_behavior =3D JSON_BEHAVIOR_ERROR;
+
+ appendStringInfoString(buf, " PATH ");
+
+ get_json_path_spec(colexpr->path_spec, context, showimplicit);
+
+ get_json_expr_options(colexpr, context, default_behavior);
+ }
+
+ if (node->child)
+ get_json_table_nested_columns(tf, node->child, context, showimplicit,
+ node->colMax >=3D node->colMin);
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel -=3D PRETTYINDENT_VAR;
+
+ appendContextKeyword(context, ")", 0, 0, 0);
+}
+
+/* ----------
+ * get_json_table - Parse back a JSON_TABLE function
+ * ----------
+ */
+static void
+get_json_table(TableFunc *tf, deparse_context *context, bool showimplici=
t)
+{
+ StringInfo buf =3D context->buf;
+ JsonExpr *jexpr =3D castNode(JsonExpr, tf->docexpr);
+ JsonTableParentNode *root =3D castNode(JsonTableParentNode, tf->plan);
+
+ appendStringInfoString(buf, "JSON_TABLE(");
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel +=3D PRETTYINDENT_VAR;
+
+ appendContextKeyword(context, "", 0, 0, 0);
+
+ get_rule_expr(jexpr->formatted_expr, context, showimplicit);
+
+ appendStringInfoString(buf, ", ");
+
+ get_const_expr(root->path, context, -1);
+
+ if (jexpr->passing_values)
+ {
+ ListCell *lc1, *lc2;
+ bool needcomma =3D false;
+
+ appendStringInfoChar(buf, ' ');
+ appendContextKeyword(context, "PASSING ", 0, 0, 0);
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel +=3D PRETTYINDENT_VAR;
+
+ forboth(lc1, jexpr->passing_names,
+ lc2, jexpr->passing_values)
+ {
+ if (needcomma)
+ appendStringInfoString(buf, ", ");
+ needcomma =3D true;
+
+ appendContextKeyword(context, "", 0, 0, 0);
+
+ get_rule_expr((Node *) lfirst(lc2), context, false);
+ appendStringInfo(buf, " AS %s",
+ quote_identifier((lfirst_node(String, lc1))->val));
+ }
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel -=3D PRETTYINDENT_VAR;
+ }
+
+ get_json_table_columns(tf, root, context, showimplicit);
+
+ if (jexpr->on_error->btype !=3D JSON_BEHAVIOR_EMPTY)
+ get_json_behavior(jexpr->on_error, context, "ERROR");
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel -=3D PRETTYINDENT_VAR;
+
+ appendContextKeyword(context, ")", 0, 0, 0);
+}
+
+/* ----------
+ * get_tablefunc - Parse back a table function
+ * ----------
+ */
+static void
+get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit=
)
+{
+ /* XMLTABLE and JSON_TABLE are the only existing implementations. */
+
+ if (tf->functype =3D=3D TFT_XMLTABLE)
+ get_xmltable(tf, context, showimplicit);
+ else if (tf->functype =3D=3D TFT_JSON_TABLE)
+ get_json_table(tf, context, showimplicit);
+}
+
/* ----------
* get_from_clause - Parse back a FROM clause
*
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/mis=
c/queryjumble.c
index 9b1252f06d..fa1374f2fd 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -878,9 +878,11 @@ JumbleExpr(JumbleState *jstate, Node *node)
{
TableFunc *tablefunc =3D (TableFunc *) node;
=20
+ APP_JUMB(tablefunc->functype);
JumbleExpr(jstate, tablefunc->docexpr);
JumbleExpr(jstate, tablefunc->rowexpr);
JumbleExpr(jstate, (Node *) tablefunc->colexprs);
+ JumbleExpr(jstate, (Node *) tablefunc->colvalexprs);
}
break;
case T_TableSampleClause:
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execE=
xpr.h
index 00916724d9..0abe9d1ee4 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -850,6 +850,10 @@ extern Datum ExecPrepareJsonItemCoercion(struct Json=
bValue *item,
struct JsonCoercionState **pjcstate);
extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
struct JsonCoercionsState *);
+extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
+ ExprContext *econtext, bool *isnull,
+ Datum caseval_datum,
+ bool caseval_isnull);
=20
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertra=
ns, AggStatePerGroup pergroup,
ExprContext *aggcontext);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 5edf7aecee..40a8d54594 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -211,6 +211,8 @@ typedef enum NodeTag
T_JsonExpr,
T_JsonCoercion,
T_JsonItemCoercions,
+ T_JsonTableParentNode,
+ T_JsonTableSiblingNode,
=20
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -507,6 +509,9 @@ typedef enum NodeTag
T_JsonFuncExpr,
T_JsonIsPredicate,
T_JsonExistsPredicate,
+ T_JsonTable,
+ T_JsonTableColumn,
+ T_JsonTablePlan,
T_JsonCommon,
T_JsonArgument,
T_JsonKeyValue,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenode=
s.h
index f8390ee9c0..72f0e6aa79 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1563,6 +1563,19 @@ typedef enum JsonQuotes
JS_QUOTES_OMIT /* OMIT QUOTES */
} JsonQuotes;
=20
+/*
+ * JsonTableColumnType -
+ * enumeration of JSON_TABLE column types
+ */
+typedef enum
+{
+ JTC_FOR_ORDINALITY,
+ JTC_REGULAR,
+ JTC_EXISTS,
+ JTC_FORMATTED,
+ JTC_NESTED,
+} JsonTableColumnType;
+
/*
* JsonPathSpec -
* representation of JSON path constant
@@ -1622,6 +1635,41 @@ typedef struct JsonFuncExpr
int location; /* token location, or -1 if unknown */
} JsonFuncExpr;
=20
+/*
+ * JsonTableColumn -
+ * untransformed representation of JSON_TABLE column
+ */
+typedef struct JsonTableColumn
+{
+ NodeTag type;
+ JsonTableColumnType coltype; /* column type */
+ char *name; /* column name */
+ TypeName *typeName; /* column type name */
+ JsonPathSpec pathspec; /* path specification, if any */
+ JsonFormat *format; /* JSON format clause, if specified */
+ JsonWrapper wrapper; /* WRAPPER behavior for formatted columns */
+ bool omit_quotes; /* omit or keep quotes on scalar strings? */
+ List *columns; /* nested columns */
+ JsonBehavior *on_empty; /* ON EMPTY behavior */
+ JsonBehavior *on_error; /* ON ERROR behavior */
+ int location; /* token location, or -1 if unknown */
+} JsonTableColumn;
+
+/*
+ * JsonTable -
+ * untransformed representation of JSON_TABLE
+ */
+typedef struct JsonTable
+{
+ NodeTag type;
+ JsonCommon *common; /* common JSON path syntax fields */
+ List *columns; /* list of JsonTableColumn */
+ JsonBehavior *on_error; /* ON ERROR behavior, if specified */
+ Alias *alias; /* table alias in FROM clause */
+ bool lateral; /* does it have LATERAL prefix? */
+ int location; /* token location, or -1 if unknown */
+} JsonTable;
+
/*
* JsonKeyValue -
* untransformed representation of JSON object key-value pair for
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.=
h
index 0af3d0bb2f..da8cd5fbd5 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -73,6 +73,12 @@ typedef struct RangeVar
int location; /* token location, or -1 if unknown */
} RangeVar;
=20
+typedef enum TableFuncType
+{
+ TFT_XMLTABLE,
+ TFT_JSON_TABLE
+} TableFuncType;
+
/*
* TableFunc - node for a table function, such as XMLTABLE.
*
@@ -82,6 +88,7 @@ typedef struct RangeVar
typedef struct TableFunc
{
NodeTag type;
+ TableFuncType functype; /* XMLTABLE or JSON_TABLE */
List *ns_uris; /* list of namespace URI expressions */
List *ns_names; /* list of namespace names or NULL */
Node *docexpr; /* input document expression */
@@ -92,7 +99,9 @@ typedef struct TableFunc
List *colcollations; /* OID list of column collation OIDs */
List *colexprs; /* list of column filter expressions */
List *coldefexprs; /* list of column default expressions */
+ List *colvalexprs; /* list of column value expressions */
Bitmapset *notnulls; /* nullability flag for each output column */
+ Node *plan; /* JSON_TABLE plan */
int ordinalitycol; /* counts from 0; -1 if none specified */
int location; /* token location, or -1 if unknown */
} TableFunc;
@@ -1243,7 +1252,8 @@ typedef enum JsonExprOp
{
IS_JSON_VALUE, /* JSON_VALUE() */
IS_JSON_QUERY, /* JSON_QUERY() */
- IS_JSON_EXISTS /* JSON_EXISTS() */
+ IS_JSON_EXISTS, /* JSON_EXISTS() */
+ IS_JSON_TABLE /* JSON_TABLE() */
} JsonExprOp;
=20
/*
@@ -1457,6 +1467,31 @@ typedef struct JsonExpr
int location; /* token location, or -1 if unknown */
} JsonExpr;
=20
+/*
+ * JsonTableParentNode -
+ * transformed representation of parent JSON_TABLE plan node
+ */
+typedef struct JsonTableParentNode
+{
+ NodeTag type;
+ Const *path; /* jsonpath constant */
+ Node *child; /* nested columns, if any */
+ int colMin; /* min column index in the resulting column list */
+ int colMax; /* max column index in the resulting column list */
+ bool errorOnError; /* ERROR/EMPTY ON ERROR behavior */
+} JsonTableParentNode;
+
+/*
+ * JsonTableSiblingNode -
+ * transformed representation of joined sibling JSON_TABLE plan node
+ */
+typedef struct JsonTableSiblingNode
+{
+ NodeTag type;
+ Node *larg; /* left join node */
+ Node *rarg; /* right join node */
+} JsonTableSiblingNode;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 458f55fbec..15f586d455 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -241,6 +241,7 @@ PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME=
_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_query", JSON_QUERY, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_scalar", JSON_SCALAR, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_serialize", JSON_SERIALIZE, COL_NAME_KEYWORD, BARE_LABE=
L)
+PG_KEYWORD("json_table", JSON_TABLE, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_value", JSON_VALUE, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("keep", KEEP, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -281,6 +282,7 @@ PG_KEYWORD("names", NAMES, UNRESERVED_KEYWORD, BARE_L=
ABEL)
PG_KEYWORD("national", NATIONAL, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("natural", NATURAL, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("nchar", NCHAR, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("nested", NESTED, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("new", NEW, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("next", NEXT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("nfc", NFC, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -330,6 +332,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BA=
RE_LABEL)
PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse=
_clause.h
index 0eab3c03e8..9216f81acb 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -51,4 +51,7 @@ extern List *addTargetToSortList(ParseState *pstate, Ta=
rgetEntry *tle,
extern Index assignSortGroupRef(TargetEntry *tle, List *tlist);
extern bool targetIsInSortList(TargetEntry *tle, Oid sortop, List *sortL=
ist);
=20
+/* functions in parse_jsontable.c */
+extern ParseNamespaceItem *transformJsonTable(ParseState *pstate, JsonTa=
ble *jt);
+
#endif /* PARSE_CLAUSE_H */
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index fd2663c22f..3c37ea087a 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -15,6 +15,7 @@
#define JSONPATH_H
=20
#include "fmgr.h"
+#include "executor/tablefunc.h"
#include "nodes/pg_list.h"
#include "nodes/primnodes.h"
#include "utils/jsonb.h"
@@ -263,6 +264,7 @@ typedef struct JsonPathVariableEvalContext
int32 typmod;
struct ExprContext *econtext;
struct ExprState *estate;
+ MemoryContext mcxt; /* memory context for cached value */
Datum value;
bool isnull;
bool evaluated;
@@ -281,4 +283,6 @@ extern JsonbValue *JsonPathValue(Datum jb, JsonPath *=
jp, bool *empty,
extern int EvalJsonPathVar(void *vars, char *varName, int varNameLen,
JsonbValue *val, JsonbValue *baseObject);
=20
+extern const TableFuncRoutine JsonbTableRoutine;
+
#endif
diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regres=
s/expected/json_sqljson.out
index bb62634314..3096a5f91c 100644
--- a/src/test/regress/expected/json_sqljson.out
+++ b/src/test/regress/expected/json_sqljson.out
@@ -13,3 +13,8 @@ SELECT JSON_QUERY(NULL FORMAT JSON, '$');
ERROR: JSON_QUERY() is not yet implemented for json type
LINE 1: SELECT JSON_QUERY(NULL FORMAT JSON, '$');
^
+-- JSON_TABLE
+SELECT * FROM JSON_TABLE(NULL FORMAT JSON, '$' COLUMNS (foo text));
+ERROR: JSON_TABLE() is not yet implemented for json type
+LINE 1: SELECT * FROM JSON_TABLE(NULL FORMAT JSON, '$' COLUMNS (foo ...
+ ^
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regre=
ss/expected/jsonb_sqljson.out
index f2f5e271b8..d89c3bef44 100644
--- a/src/test/regress/expected/jsonb_sqljson.out
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -1016,3 +1016,565 @@ CREATE INDEX ON test_jsonb_mutability (JSON_QUERY=
(js, '$[1, 0 to $.a ? (@.dateti
ERROR: functions in index expression must be marked IMMUTABLE
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.dat=
etime("HH:MI") =3D=3D $x)]' PASSING '12:34'::time AS x));
DROP TABLE test_jsonb_mutability;
+-- JSON_TABLE
+-- Should fail (JSON_TABLE can be used only in FROM clause)
+SELECT JSON_TABLE('[]', '$');
+ERROR: syntax error at or near "("
+LINE 1: SELECT JSON_TABLE('[]', '$');
+ ^
+-- Should fail (no columns)
+SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ());
+ERROR: syntax error at or near ")"
+LINE 1: SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ());
+ ^
+-- NULL =3D> empty table
+SELECT * FROM JSON_TABLE(NULL::jsonb, '$' COLUMNS (foo int)) bar;
+ foo=20
+-----
+(0 rows)
+
+--
+SELECT * FROM JSON_TABLE(jsonb '123', '$'
+ COLUMNS (item int PATH '$', foo int)) bar;
+ item | foo=20
+------+-----
+ 123 | =20
+(1 row)
+
+-- JSON_TABLE: basic functionality
+CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
+SELECT *
+FROM
+ (VALUES
+ ('1'),
+ ('[]'),
+ ('{}'),
+ ('[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[=
1,2]", "\"str\""]')
+ ) vals(js)
+ LEFT OUTER JOIN
+-- JSON_TABLE is implicitly lateral
+ JSON_TABLE(
+ vals.js::jsonb, 'lax $[*]'
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY, -- allowed additional ordinality columns
+ "int" int PATH '$',
+ "text" text PATH '$',
+ "char(4)" char(4) PATH '$',
+ "bool" bool PATH '$',
+ "numeric" numeric PATH '$',
+ "domain" jsonb_test_domain PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc char(4) FORMAT JSON PATH '$',
+ jsv varchar(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSON PATH '$',
+ jsbq jsonb FORMAT JSON PATH '$' OMIT QUOTES,
+ aaa int, -- implicit path '$."aaa"',
+ aaa1 int PATH '$.aaa',
+ exists1 bool EXISTS PATH '$.aaa',
+ exists2 int EXISTS PATH '$.aaa',
+ exists3 int EXISTS PATH 'strict $.aaa' UNKNOWN ON ERROR,
+ exists4 text EXISTS PATH 'strict $.aaa' FALSE ON ERROR,
+ js2 json PATH '$',
+ jsb2w jsonb PATH '$' WITH WRAPPER,
+ jsb2q jsonb PATH '$' OMIT QUOTES,
+ ia int[] PATH '$',
+ ta text[] PATH '$',
+ jba jsonb[] PATH '$'
+ )
+ ) jt
+ ON true;
+ js =
| id | id2 | int | text | char(4) | bool | numeric | do=
main | js | jb | jst | jsc | jsv | js=
b | jsbq | aaa | aaa1 | exists1 | exists2 | exists3 | exists=
4 | js2 | jsb2w | jsb2q | ia | ta | jba=20
+------------------------------------------------------------------------=
---------------+----+-----+-----+---------+---------+------+---------+---=
------+--------------+--------------+--------------+------+------+-------=
-------+--------------+-----+------+---------+---------+---------+-------=
--+--------------+----------------+--------------+----+----+-----
+ 1 =
| 1 | 1 | 1 | 1 | 1 | | 1 | 1 =
| 1 | 1 | 1 | 1 | 1 | 1 =
| 1 | | | f | 0 | | false =
| 1 | [1] | 1 | | |=20
+ [] =
| | | | | | | | =
| | | | | | =
| | | | | | | =
| | | | | |=20
+ {} =
| 1 | 1 | | | | | | =
| {} | {} | {} | {} | {} | {} =
| {} | | | f | 0 | | false =
| {} | [{}] | {} | | |=20
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2=
]", "\"str\""] | 1 | 1 | 1 | 1 | 1 | | 1 | 1 =
| 1 | 1 | 1 | 1 | 1 | 1 =
| 1 | | | f | 0 | | false =
| 1 | [1] | 1 | | |=20
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2=
]", "\"str\""] | 2 | 2 | 1 | 1.23 | 1.23 | | 1.23 | 1.=
23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 =
| 1.23 | | | f | 0 | | false =
| 1.23 | [1.23] | 1.23 | | |=20
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2=
]", "\"str\""] | 3 | 3 | 2 | 2 | 2 | | 2 | 2 =
| "2" | "2" | "2" | "2" | "2" | "2" =
| 2 | | | f | 0 | | false =
| "2" | ["2"] | 2 | | |=20
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2=
]", "\"str\""] | 4 | 4 | | aaaaaaa | aaaa | | | aa=
aaaaa | "aaaaaaa" | "aaaaaaa" | "aaaaaaa" | "aaa | "aaa | "aaaaa=
aa" | | | | f | 0 | | false =
| "aaaaaaa" | ["aaaaaaa"] | | | |=20
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2=
]", "\"str\""] | 5 | 5 | | foo | foo | | | =
| "foo" | "foo" | "foo" | "foo | "foo | "foo" =
| | | | f | 0 | | false =
| "foo" | ["foo"] | | | |=20
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2=
]", "\"str\""] | 6 | 6 | | | | | | =
| null | null | null | null | null | null =
| null | | | f | 0 | | false =
| null | [null] | null | | |=20
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2=
]", "\"str\""] | 7 | 7 | 0 | false | fals | f | | fa=
lse | false | false | false | fals | fals | false =
| false | | | f | 0 | | false =
| false | [false] | false | | |=20
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2=
]", "\"str\""] | 8 | 8 | 1 | true | true | t | | tr=
ue | true | true | true | true | true | true =
| true | | | f | 0 | | false =
| true | [true] | true | | |=20
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2=
]", "\"str\""] | 9 | 9 | | | | | | =
| {"aaa": 123} | {"aaa": 123} | {"aaa": 123} | {"aa | {"aa | {"aaa"=
: 123} | {"aaa": 123} | 123 | 123 | t | 1 | 1 | true =
| {"aaa": 123} | [{"aaa": 123}] | {"aaa": 123} | | |=20
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2=
]", "\"str\""] | 10 | 10 | | [1,2] | [1,2 | | | [1=
,2] | "[1,2]" | "[1,2]" | "[1,2]" | "[1, | "[1, | "[1,2]=
" | [1, 2] | | | f | 0 | | false =
| "[1,2]" | ["[1,2]"] | [1, 2] | | |=20
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2=
]", "\"str\""] | 11 | 11 | | "str" | "str | | | "s=
tr" | "\"str\"" | "\"str\"" | "\"str\"" | "\"s | "\"s | "\"str=
\"" | "str" | | | f | 0 | | false =
| "\"str\"" | ["\"str\""] | "str" | | |=20
+(14 rows)
+
+-- JSON_TABLE: Test backward parsing
+CREATE VIEW jsonb_table_view AS
+SELECT * FROM
+ JSON_TABLE(
+ jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY, -- allowed additional ordinality columns
+ "int" int PATH '$',
+ "text" text PATH '$',
+ "char(4)" char(4) PATH '$',
+ "bool" bool PATH '$',
+ "numeric" numeric PATH '$',
+ "domain" jsonb_test_domain PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc char(4) FORMAT JSON PATH '$',
+ jsv varchar(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSON PATH '$',
+ jsbq jsonb FORMAT JSON PATH '$' OMIT QUOTES,
+ aaa int, -- implicit path '$."aaa"',
+ aaa1 int PATH '$.aaa',
+ exists1 bool EXISTS PATH '$.aaa',
+ exists2 int EXISTS PATH '$.aaa' TRUE ON ERROR,
+ exists3 text EXISTS PATH 'strict $.aaa' UNKNOWN ON ERROR,
+ js2 json PATH '$',
+ jsb2w jsonb PATH '$' WITH WRAPPER,
+ jsb2q jsonb PATH '$' OMIT QUOTES,
+ ia int[] PATH '$',
+ ta text[] PATH '$',
+ jba jsonb[] PATH '$',
+ NESTED PATH '$[1]' COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' COLUMNS (
+ NESTED PATH '$[*]' COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
+\sv jsonb_table_view
+CREATE OR REPLACE VIEW public.jsonb_table_view AS
+ SELECT "json_table".id,
+ "json_table".id2,
+ "json_table"."int",
+ "json_table".text,
+ "json_table"."char(4)",
+ "json_table".bool,
+ "json_table"."numeric",
+ "json_table".domain,
+ "json_table".js,
+ "json_table".jb,
+ "json_table".jst,
+ "json_table".jsc,
+ "json_table".jsv,
+ "json_table".jsb,
+ "json_table".jsbq,
+ "json_table".aaa,
+ "json_table".aaa1,
+ "json_table".exists1,
+ "json_table".exists2,
+ "json_table".exists3,
+ "json_table".js2,
+ "json_table".jsb2w,
+ "json_table".jsb2q,
+ "json_table".ia,
+ "json_table".ta,
+ "json_table".jba,
+ "json_table".a1,
+ "json_table".b1,
+ "json_table".a11,
+ "json_table".a21,
+ "json_table".a22
+ FROM JSON_TABLE(
+ 'null'::jsonb, '$[*]'
+ PASSING
+ 1 + 2 AS a,
+ '"foo"'::json AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY,
+ "int" integer PATH '$',
+ text text PATH '$',
+ "char(4)" character(4) PATH '$',
+ bool boolean PATH '$',
+ "numeric" numeric PATH '$',
+ domain jsonb_test_domain PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc character(4) FORMAT JSON PATH '$',
+ jsv character varying(4) FORMAT JSON PATH '$',
+ jsb jsonb PATH '$',
+ jsbq jsonb PATH '$' OMIT QUOTES,
+ aaa integer PATH '$."aaa"',
+ aaa1 integer PATH '$."aaa"',
+ exists1 boolean EXISTS PATH '$."aaa"',
+ exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR,
+ exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERR=
OR,
+ js2 json PATH '$',
+ jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER,
+ jsb2q jsonb PATH '$' OMIT QUOTES,
+ ia integer[] PATH '$',
+ ta text[] PATH '$',
+ jba jsonb[] PATH '$',
+ NESTED PATH '$[1]'
+ COLUMNS (
+ a1 integer PATH '$."a1"',
+ b1 text PATH '$."b1"',
+ NESTED PATH '$[*]'
+ COLUMNS (
+ a11 text PATH '$."a11"'
+ )
+ ),
+ NESTED PATH '$[2]'
+ COLUMNS (
+ NESTED PATH '$[*]'
+ COLUMNS (
+ a21 text PATH '$."a21"'
+ ),
+ NESTED PATH '$[*]'
+ COLUMNS (
+ a22 text PATH '$."a22"'
+ )
+ )
+ )
+ )
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
+ =
=
=
=
=
=
=
QUERY P=
LAN =
=
=
=
=
=
=
=20
+------------------------------------------------------------------------=
-------------------------------------------------------------------------=
-------------------------------------------------------------------------=
-------------------------------------------------------------------------=
-------------------------------------------------------------------------=
-------------------------------------------------------------------------=
-------------------------------------------------------------------------=
-------------------------------------------------------------------------=
-------------------------------------------------------------------------=
-------------------------------------------------------------------------=
-------------------------------------------------------------------------=
-------------------------------------------------------------------------=
-------------------------------------------------------------------------=
-------------------------------------------------------------------------=
-------------------------------------------------------------------------=
--------------------------------------------------------------------
+ Table Function Scan on "json_table"
+ Output: "json_table".id, "json_table".id2, "json_table"."int", "json_=
table".text, "json_table"."char(4)", "json_table".bool, "json_table"."num=
eric", "json_table".domain, "json_table".js, "json_table".jb, "json_table=
".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table"=
=2Ejsbq, "json_table".aaa, "json_table".aaa1, "json_table".exists1, "json=
_table".exists2, "json_table".exists3, "json_table".js2, "json_table".jsb=
2w, "json_table".jsb2q, "json_table".ia, "json_table".ta, "json_table".jb=
a, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, =
"json_table".a22
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' PASSING 3 AS a,=
'"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, =
"int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '=
$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_=
domain PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSO=
N PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(=
4) FORMAT JSON PATH '$', jsb jsonb PATH '$', jsbq jsonb PATH '$' OMIT QUO=
TES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', exists1 boo=
lean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON=
ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR, js2 j=
son PATH '$', jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER, jsb2q json=
b PATH '$' OMIT QUOTES, ia integer[] PATH '$', ta text[] PATH '$', jba js=
onb[] PATH '$', NESTED PATH '$[1]' COLUMNS (a1 integer PATH '$."a1"', b1 =
text PATH '$."b1"', NESTED PATH '$[*]' COLUMNS (a11 text PATH '$."a11"'))=
, NESTED PATH '$[2]' COLUMNS ( NESTED PATH '$[*]' COLUMNS (a21 text PATH =
'$."a21"'), NESTED PATH '$[*]' COLUMNS (a22 text PATH '$."a22"'))))
+(3 rows)
+
+DROP VIEW jsonb_table_view;
+DROP DOMAIN jsonb_test_domain;
+-- JSON_TABLE: ON EMPTY/ON ERROR behavior
+SELECT *
+FROM
+ (VALUES ('1'), ('"err"')) vals(js),
+ JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$')) jt;
+ js | a=20
+-------+---
+ 1 | 1
+ "err" | =20
+(2 rows)
+
+SELECT *
+FROM
+ (VALUES ('1'), ('"err"')) vals(js)
+ LEFT OUTER JOIN
+ JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$') ERROR ON ERROR)=
jt
+ ON true;
+ERROR: invalid input syntax for type integer: "err"
+SELECT *
+FROM
+ (VALUES ('1'), ('"err"')) vals(js)
+ LEFT OUTER JOIN
+ JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR))=
jt
+ ON true;
+ERROR: invalid input syntax for type integer: "err"
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR =
ON EMPTY)) jt;
+ a=20
+---
+ =20
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a'=
ERROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: jsonpath member accessor can only be applied to an object
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ER=
ROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: no SQL/JSON item
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFA=
ULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a=20
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.=
a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a=20
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' =
DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a=20
+---
+ 1
+(1 row)
+
+-- JSON_TABLE: EXISTS PATH types
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int4 EXISTS PATH '$=
=2Ea'));
+ a=20
+---
+ 0
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXISTS PATH '$=
=2Ea'));
+ERROR: cannot cast type boolean to smallint
+LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXI.=
=2E.
+ ^
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXISTS PATH '$=
=2Ea'));
+ERROR: cannot cast type boolean to bigint
+LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXI.=
=2E.
+ ^
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 EXISTS PATH =
'$.a'));
+ERROR: cannot cast type boolean to real
+LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 E.=
=2E.
+ ^
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH=
'$.a'));
+ a =20
+-----
+ fal
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXISTS PATH '$=
=2Ea'));
+ERROR: cannot cast type boolean to json
+LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXI.=
=2E.
+ ^
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EXISTS PATH '=
$.a'));
+ERROR: cannot cast type boolean to jsonb
+LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EX.=
=2E.
+ ^
+-- JSON_TABLE: nested paths and plans
+-- Should fail (column names anf path names shall be distinct)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$'
+ COLUMNS (
+ a int,
+ b text,
+ a jsonb
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column name: a
+HINT: JSON_TABLE path names and column names shall be distinct from one=
another
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$'
+ COLUMNS (
+ b int,
+ NESTED PATH '$'
+ COLUMNS (
+ c int,
+ b text
+ )
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column name: b
+HINT: JSON_TABLE path names and column names shall be distinct from one=
another
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$'
+ COLUMNS (
+ NESTED PATH '$'
+ COLUMNS (
+ b int
+ ),
+ NESTED PATH '$'
+ COLUMNS (
+ NESTED PATH '$'
+ COLUMNS (
+ c int,
+ b text
+ )
+ )
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column name: b
+HINT: JSON_TABLE path names and column names shall be distinct from one=
another
+-- JSON_TABLE: plan execution
+CREATE TEMP TABLE jsonb_table_test (js jsonb);
+INSERT INTO jsonb_table_test
+VALUES (
+ '[
+ {"a": 1, "b": [], "c": []},
+ {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]},
+ {"a": 3, "b": [1, 2], "c": []},
+ {"x": "4", "b": [1, 2], "c": 123}
+ ]'
+);
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]'
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' columns ( b int path '$' ),
+ nested path 'strict $.c[*]' columns ( c int path '$' )
+ )
+ ) jt;
+ n | a | b | c =20
+---+----+---+----
+ 1 | 1 | | =20
+ 2 | 2 | 1 | =20
+ 2 | 2 | 2 | =20
+ 2 | 2 | 3 | =20
+ 2 | 2 | | 10
+ 2 | 2 | | =20
+ 2 | 2 | | 20
+ 3 | 3 | 1 | =20
+ 3 | 3 | 2 | =20
+ 4 | -1 | 1 | =20
+ 4 | -1 | 2 | =20
+(11 rows)
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(jsonb
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >=3D $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+ x | y | y | z=20
+---+---+--------------+---
+ 2 | 1 | [1, 2, 3] | 1
+ 2 | 1 | [1, 2, 3] | 2
+ 2 | 1 | [1, 2, 3] | 3
+ 3 | 1 | [1, 2, 3] | 1
+ 3 | 1 | [1, 2, 3] | 2
+ 3 | 1 | [1, 2, 3] | 3
+ 3 | 1 | [2, 3, 4, 5] | 2
+ 3 | 1 | [2, 3, 4, 5] | 3
+ 3 | 1 | [2, 3, 4, 5] | 4
+ 3 | 1 | [2, 3, 4, 5] | 5
+ 4 | 1 | [1, 2, 3] | 1
+ 4 | 1 | [1, 2, 3] | 2
+ 4 | 1 | [1, 2, 3] | 3
+ 4 | 1 | [2, 3, 4, 5] | 2
+ 4 | 1 | [2, 3, 4, 5] | 3
+ 4 | 1 | [2, 3, 4, 5] | 4
+ 4 | 1 | [2, 3, 4, 5] | 5
+ 4 | 1 | [3, 4, 5, 6] | 3
+ 4 | 1 | [3, 4, 5, 6] | 4
+ 4 | 1 | [3, 4, 5, 6] | 5
+ 4 | 1 | [3, 4, 5, 6] | 6
+ 2 | 2 | [1, 2, 3] | 2
+ 2 | 2 | [1, 2, 3] | 3
+ 3 | 2 | [1, 2, 3] | 2
+ 3 | 2 | [1, 2, 3] | 3
+ 3 | 2 | [2, 3, 4, 5] | 2
+ 3 | 2 | [2, 3, 4, 5] | 3
+ 3 | 2 | [2, 3, 4, 5] | 4
+ 3 | 2 | [2, 3, 4, 5] | 5
+ 4 | 2 | [1, 2, 3] | 2
+ 4 | 2 | [1, 2, 3] | 3
+ 4 | 2 | [2, 3, 4, 5] | 2
+ 4 | 2 | [2, 3, 4, 5] | 3
+ 4 | 2 | [2, 3, 4, 5] | 4
+ 4 | 2 | [2, 3, 4, 5] | 5
+ 4 | 2 | [3, 4, 5, 6] | 3
+ 4 | 2 | [3, 4, 5, 6] | 4
+ 4 | 2 | [3, 4, 5, 6] | 5
+ 4 | 2 | [3, 4, 5, 6] | 6
+ 2 | 3 | [1, 2, 3] | 3
+ 3 | 3 | [1, 2, 3] | 3
+ 3 | 3 | [2, 3, 4, 5] | 3
+ 3 | 3 | [2, 3, 4, 5] | 4
+ 3 | 3 | [2, 3, 4, 5] | 5
+ 4 | 3 | [1, 2, 3] | 3
+ 4 | 3 | [2, 3, 4, 5] | 3
+ 4 | 3 | [2, 3, 4, 5] | 4
+ 4 | 3 | [2, 3, 4, 5] | 5
+ 4 | 3 | [3, 4, 5, 6] | 3
+ 4 | 3 | [3, 4, 5, 6] | 4
+ 4 | 3 | [3, 4, 5, 6] | 5
+ 4 | 3 | [3, 4, 5, 6] | 6
+(52 rows)
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ jsonb '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+ERROR: could not find jsonpath variable "x"
+-- Extension: non-constant JSON path
+SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a');
+ json_exists=20
+-------------
+ t
+(1 row)
+
+SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a');
+ json_value=20
+------------
+ 123
+(1 row)
+
+SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON=
EMPTY);
+ json_value=20
+------------
+ foo
+(1 row)
+
+SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a');
+ json_query=20
+------------
+ 123
+(1 row)
+
+SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
+ json_query=20
+------------
+ [123]
+(1 row)
+
+-- Should fail (invalid path)
+SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error');
+ERROR: syntax error, unexpected IDENT_P at or near " " of jsonpath inpu=
t
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (=
foo int));
+ERROR: only string constants supported in JSON_TABLE path specification=
+LINE 1: SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || '...
+ ^
+-- Test parallel JSON_VALUE()
+CREATE TABLE test_parallel_jsonb_value AS
+SELECT i::text::jsonb AS js
+FROM generate_series(1, 1000000) i;
+-- Should be non-parallel due to subtransactions
+EXPLAIN (COSTS OFF)
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jso=
nb_value;
+ QUERY PLAN =20
+---------------------------------------------
+ Aggregate
+ -> Seq Scan on test_parallel_jsonb_value
+(2 rows)
+
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jso=
nb_value;
+ sum =20
+--------------
+ 500000500000
+(1 row)
+
+-- Should be parallel
+EXPLAIN (COSTS OFF)
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM te=
st_parallel_jsonb_value;
+ QUERY PLAN =20
+------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on test_parallel_jsonb_value
+(5 rows)
+
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM te=
st_parallel_jsonb_value;
+ sum =20
+--------------
+ 500000500000
+(1 row)
+
diff --git a/src/test/regress/sql/json_sqljson.sql b/src/test/regress/sql=
/json_sqljson.sql
index 4f30fa46b9..df4a430d88 100644
--- a/src/test/regress/sql/json_sqljson.sql
+++ b/src/test/regress/sql/json_sqljson.sql
@@ -9,3 +9,7 @@ SELECT JSON_VALUE(NULL FORMAT JSON, '$');
-- JSON_QUERY
=20
SELECT JSON_QUERY(NULL FORMAT JSON, '$');
+
+-- JSON_TABLE
+
+SELECT * FROM JSON_TABLE(NULL FORMAT JSON, '$' COLUMNS (foo text));
diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sq=
l/jsonb_sqljson.sql
index 00a067a06a..6614dd6e45 100644
--- a/src/test/regress/sql/jsonb_sqljson.sql
+++ b/src/test/regress/sql/jsonb_sqljson.sql
@@ -315,3 +315,287 @@ CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(j=
s, '$[1, $.a ? (@.datetime()
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (=
@.datetime() =3D=3D $x)]' PASSING '12:34'::time AS x));
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.dat=
etime("HH:MI") =3D=3D $x)]' PASSING '12:34'::time AS x));
DROP TABLE test_jsonb_mutability;
+
+-- JSON_TABLE
+
+-- Should fail (JSON_TABLE can be used only in FROM clause)
+SELECT JSON_TABLE('[]', '$');
+
+-- Should fail (no columns)
+SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ());
+
+-- NULL =3D> empty table
+SELECT * FROM JSON_TABLE(NULL::jsonb, '$' COLUMNS (foo int)) bar;
+
+--
+SELECT * FROM JSON_TABLE(jsonb '123', '$'
+ COLUMNS (item int PATH '$', foo int)) bar;
+
+-- JSON_TABLE: basic functionality
+CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
+
+SELECT *
+FROM
+ (VALUES
+ ('1'),
+ ('[]'),
+ ('{}'),
+ ('[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[=
1,2]", "\"str\""]')
+ ) vals(js)
+ LEFT OUTER JOIN
+-- JSON_TABLE is implicitly lateral
+ JSON_TABLE(
+ vals.js::jsonb, 'lax $[*]'
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY, -- allowed additional ordinality columns
+ "int" int PATH '$',
+ "text" text PATH '$',
+ "char(4)" char(4) PATH '$',
+ "bool" bool PATH '$',
+ "numeric" numeric PATH '$',
+ "domain" jsonb_test_domain PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc char(4) FORMAT JSON PATH '$',
+ jsv varchar(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSON PATH '$',
+ jsbq jsonb FORMAT JSON PATH '$' OMIT QUOTES,
+ aaa int, -- implicit path '$."aaa"',
+ aaa1 int PATH '$.aaa',
+ exists1 bool EXISTS PATH '$.aaa',
+ exists2 int EXISTS PATH '$.aaa',
+ exists3 int EXISTS PATH 'strict $.aaa' UNKNOWN ON ERROR,
+ exists4 text EXISTS PATH 'strict $.aaa' FALSE ON ERROR,
+
+ js2 json PATH '$',
+ jsb2w jsonb PATH '$' WITH WRAPPER,
+ jsb2q jsonb PATH '$' OMIT QUOTES,
+ ia int[] PATH '$',
+ ta text[] PATH '$',
+ jba jsonb[] PATH '$'
+ )
+ ) jt
+ ON true;
+
+-- JSON_TABLE: Test backward parsing
+
+CREATE VIEW jsonb_table_view AS
+SELECT * FROM
+ JSON_TABLE(
+ jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY, -- allowed additional ordinality columns
+ "int" int PATH '$',
+ "text" text PATH '$',
+ "char(4)" char(4) PATH '$',
+ "bool" bool PATH '$',
+ "numeric" numeric PATH '$',
+ "domain" jsonb_test_domain PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc char(4) FORMAT JSON PATH '$',
+ jsv varchar(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSON PATH '$',
+ jsbq jsonb FORMAT JSON PATH '$' OMIT QUOTES,
+ aaa int, -- implicit path '$."aaa"',
+ aaa1 int PATH '$.aaa',
+ exists1 bool EXISTS PATH '$.aaa',
+ exists2 int EXISTS PATH '$.aaa' TRUE ON ERROR,
+ exists3 text EXISTS PATH 'strict $.aaa' UNKNOWN ON ERROR,
+
+ js2 json PATH '$',
+ jsb2w jsonb PATH '$' WITH WRAPPER,
+ jsb2q jsonb PATH '$' OMIT QUOTES,
+ ia int[] PATH '$',
+ ta text[] PATH '$',
+ jba jsonb[] PATH '$',
+
+ NESTED PATH '$[1]' COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' COLUMNS (
+ NESTED PATH '$[*]' COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
+
+\sv jsonb_table_view
+
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
+
+DROP VIEW jsonb_table_view;
+DROP DOMAIN jsonb_test_domain;
+
+-- JSON_TABLE: ON EMPTY/ON ERROR behavior
+SELECT *
+FROM
+ (VALUES ('1'), ('"err"')) vals(js),
+ JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$')) jt;
+
+SELECT *
+FROM
+ (VALUES ('1'), ('"err"')) vals(js)
+ LEFT OUTER JOIN
+ JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$') ERROR ON ERROR)=
jt
+ ON true;
+
+SELECT *
+FROM
+ (VALUES ('1'), ('"err"')) vals(js)
+ LEFT OUTER JOIN
+ JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR))=
jt
+ ON true;
+
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR =
ON EMPTY)) jt;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a'=
ERROR ON EMPTY) ERROR ON ERROR) jt;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ER=
ROR ON EMPTY) ERROR ON ERROR) jt;
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFA=
ULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.=
a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' =
DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+
+-- JSON_TABLE: EXISTS PATH types
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int4 EXISTS PATH '$=
=2Ea'));
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXISTS PATH '$=
=2Ea'));
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXISTS PATH '$=
=2Ea'));
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 EXISTS PATH =
'$.a'));
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH=
'$.a'));
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXISTS PATH '$=
=2Ea'));
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EXISTS PATH '=
$.a'));
+
+-- JSON_TABLE: nested paths and plans
+
+-- Should fail (column names anf path names shall be distinct)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$'
+ COLUMNS (
+ a int,
+ b text,
+ a jsonb
+ )
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$'
+ COLUMNS (
+ b int,
+ NESTED PATH '$'
+ COLUMNS (
+ c int,
+ b text
+ )
+ )
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$'
+ COLUMNS (
+ NESTED PATH '$'
+ COLUMNS (
+ b int
+ ),
+ NESTED PATH '$'
+ COLUMNS (
+ NESTED PATH '$'
+ COLUMNS (
+ c int,
+ b text
+ )
+ )
+ )
+) jt;
+
+-- JSON_TABLE: plan execution
+
+CREATE TEMP TABLE jsonb_table_test (js jsonb);
+
+INSERT INTO jsonb_table_test
+VALUES (
+ '[
+ {"a": 1, "b": [], "c": []},
+ {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]},
+ {"a": 3, "b": [1, 2], "c": []},
+ {"x": "4", "b": [1, 2], "c": 123}
+ ]'
+);
+
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]'
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' columns ( b int path '$' ),
+ nested path 'strict $.c[*]' columns ( c int path '$' )
+ )
+ ) jt;
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(jsonb
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >=3D $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ jsonb '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+
+-- Extension: non-constant JSON path
+SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a');
+SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a');
+SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON=
EMPTY);
+SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a');
+SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
+-- Should fail (invalid path)
+SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error');
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (=
foo int));
+
+-- Test parallel JSON_VALUE()
+CREATE TABLE test_parallel_jsonb_value AS
+SELECT i::text::jsonb AS js
+FROM generate_series(1, 1000000) i;
+
+-- Should be non-parallel due to subtransactions
+EXPLAIN (COSTS OFF)
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jso=
nb_value;
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jso=
nb_value;
+
+-- Should be parallel
+EXPLAIN (COSTS OFF)
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM te=
st_parallel_jsonb_value;
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM te=
st_parallel_jsonb_value;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typede=
fs.list
index 402a6617a9..2e8513dc68 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1231,6 +1231,14 @@ JsonPathParseResult
JsonPathPredicateCallback
JsonPathString
JsonSemAction
+JsonTable
+JsonTableColumn
+JsonTableColumnType
+JsonTableContext
+JsonTableJoinState
+JsonTableParentNode
+JsonTableScanState
+JsonTableSiblingNode
JsonTokenType
JsonTransformStringValuesAction
JsonTypeCategory
@@ -2619,6 +2627,7 @@ TableFunc
TableFuncRoutine
TableFuncScan
TableFuncScanState
+TableFuncType
TableInfo
TableLikeClause
TableSampleClause
--=20
2.25.4
--------------468B109EE30CA6EBDFFE9C87
Content-Type: text/x-patch; charset=UTF-8;
name="0003-JSON_TABLE-PLAN-DEFAULT-clause-v52.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="0003-JSON_TABLE-PLAN-DEFAULT-clause-v52.patch"
view thread (112+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected]
Subject: Re: [PATCH 1/4] SQL/JSON functions
In-Reply-To: <no-message-id-660912@localhost>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox