public inbox for [email protected]
help / color / mirror / Atom feed[PATCH v21 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
95+ messages / 7 participants
[nested] [flat]
* [PATCH v21 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index eead88419f..3c419672fc 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25843,16 +25843,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 24d858d1bc..cd7f052692 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -517,7 +517,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 83927b510a..85ac8fef61 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10911,13 +10911,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9979', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 20bfe71822..0144f611c2 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--Tcb1KvpfnM4LxW2s
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v21-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v18 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8ea064144f..33f7137e43 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5b0147ee2d..08c7189470 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10894,13 +10894,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9979', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--5I6of5zJg18YgZEa
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v18-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v20 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index eead88419f..3c419672fc 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25843,16 +25843,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3324ffe16a..8390626e49 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -517,7 +517,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 186ced4a35..381f2e196f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10911,13 +10911,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9979', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--Z1Z8UV8BNhgCynIS
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v20-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v37 04/11] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 2 +-
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 8 ++++----
src/test/regress/expected/tablespace.out | 4 ++--
5 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a80d794a43a..cff999032bf 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27630,16 +27630,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c041c7630c8..5d374177a04 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -740,7 +740,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 89892647808..e2f3361bf37 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11728,13 +11728,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '6270',
descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index a656f9ad07e..bc77ad62958 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -525,8 +525,8 @@ select * from pg_ls_replslotdir('') limit 0;
(0 rows)
select * from pg_ls_tmpdir() limit 0;
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select * from pg_ls_waldir() limit 0;
@@ -542,8 +542,8 @@ select * from pg_stat_file('.') limit 0;
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 8159c9f18f1..ce5d6f73e01 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -37,8 +37,8 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.25.1
--Pk/CTwBz1VvfPIDp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v37-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v36 4/7] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 2 +-
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 8 ++++----
src/test/regress/expected/tablespace.out | 4 ++--
5 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index ba30faa8441..8767ef0d7a2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -28591,16 +28591,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c7322ed405a..eb74130e721 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0e5c977e69f..381eb0e277f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11785,13 +11785,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '6270',
descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index e54e38f54ad..77bf661627b 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -461,8 +461,8 @@ select * from pg_ls_replslotdir('') limit 0;
(0 rows)
select * from pg_ls_tmpdir() limit 0;
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select * from pg_ls_waldir() limit 0;
@@ -478,8 +478,8 @@ select * from pg_stat_file('.') limit 0;
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 8159c9f18f1..ce5d6f73e01 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -37,8 +37,8 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.1
--4ybNbZnZ8tziJ7D6
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v36-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v34 04/15] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 2 +-
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 8 ++++----
src/test/regress/expected/tablespace.out | 4 ++--
5 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c2ff924d021..6697ab14b14 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27550,16 +27550,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 1878bac7eb3..1710ebe489b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -668,7 +668,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 43ee0cdc454..19d062a36dc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11647,13 +11647,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9858',
descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d18f401248a..6c64cd6f998 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -438,8 +438,8 @@ select * from pg_ls_replslotdir('') limit 0;
(0 rows)
select * from pg_ls_tmpdir() limit 0;
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select * from pg_ls_waldir() limit 0;
@@ -455,8 +455,8 @@ select * from pg_stat_file('.') limit 0;
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index c6b9074ab02..20a7245d5e1 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -28,8 +28,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.1
--smOfPzt+Qjm5bNGJ
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v34-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v35 4/7] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 2 +-
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 8 ++++----
src/test/regress/expected/tablespace.out | 4 ++--
5 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d2a455a3e27..3931411c01f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27561,16 +27561,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 75b7bf99849..3775cae225d 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7307f44f371..142b4b8d37d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11739,13 +11739,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9858',
descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index e54e38f54ad..77bf661627b 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -461,8 +461,8 @@ select * from pg_ls_replslotdir('') limit 0;
(0 rows)
select * from pg_ls_tmpdir() limit 0;
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select * from pg_ls_waldir() limit 0;
@@ -478,8 +478,8 @@ select * from pg_stat_file('.') limit 0;
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 8159c9f18f1..ce5d6f73e01 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -37,8 +37,8 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.1
--olLTNZSltDMg5Vbm
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v35-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v32 04/11] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 2 +-
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/expected/tablespace.out | 4 ++--
5 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7948e6ec06c..7589ef7ac8a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27427,16 +27427,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 0728547ec2b..d6be8cef6e3 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -709,7 +709,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c99e1633bbf..fa293eca393 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11615,13 +11615,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9858',
descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b3a9d11b5c0..a2bec47d76c 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -270,8 +270,8 @@ select * from pg_stat_file('.') limit 0;
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 21f7f19b67f..7d8398b0ca1 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -20,8 +20,8 @@ CREATE TABLESPACE regress_tblspace LOCATION :'testtablespace';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--Bne5rrxQd65beI7a
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v32-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v31 04/11] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 2 +-
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e0099e77df..9ddd23f90e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27342,16 +27342,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 0728547ec2..d6be8cef6e 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -709,7 +709,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 39fbaacbc9..3ffdcaf3a7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11615,13 +11615,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9858',
descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b3a9d11b5c..a2bec47d76 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -270,8 +270,8 @@ select * from pg_stat_file('.') limit 0;
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index c2f6d64c5c..c146a4c129 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--qZVVwWJgpX9Jzs7f
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v31-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v33 04/11] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 2 +-
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 8 ++++----
src/test/regress/expected/tablespace.out | 4 ++--
5 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 14b8fba3123..e0b0a5ab121 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27495,16 +27495,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3921723fda6..4829b34f0f6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -710,7 +710,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b58987b713c..95746fc0dcf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11631,13 +11631,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9858',
descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index a627839bc28..0305f038369 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -295,8 +295,8 @@ select * from pg_ls_replslotdir('') limit 0;
(0 rows)
select * from pg_ls_tmpdir() limit 0;
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select * from pg_ls_waldir() limit 0;
@@ -312,8 +312,8 @@ select * from pg_stat_file('.') limit 0;
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index c6b9074ab02..20a7245d5e1 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -28,8 +28,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.1
--9CzcV6dAFIr7O1Ie
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v33-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v30 04/11] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 2 +-
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index dd9b1afde4..2adc8f7d83 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26936,16 +26936,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 8efb798b2f..243e86a2a3 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -708,7 +708,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b405aa341c..0a4caf8aed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11539,13 +11539,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '8450', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b4112ec298..0d3e88fccf 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -241,8 +241,8 @@ select * from pg_stat_file('.') limit 0;
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 5f7ce3d09f..4183a77b23 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--ZwgA9U+XZDXt4+m+
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v30-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v25 05/11] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 226923a134..4fb4ec9f33 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25829,16 +25829,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 7351615f8a..da7d4a7c11 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -517,7 +517,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 8e88c549d9..a0148e6130 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10968,13 +10968,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9979', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index fd7c3c791f..7279c83d89 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -228,8 +228,8 @@ select * from pg_stat_file('.') limit 0;
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--SBikYMzjhZGK9d4p
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v25-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v26 05/11] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 2 +-
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 79d30a1568..c68c9d548b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26491,16 +26491,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 198a99391a..da7d4a7c11 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 17b03d1abd..d2594c2e45 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11258,13 +11258,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9979', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index fd7c3c791f..7279c83d89 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -228,8 +228,8 @@ select * from pg_stat_file('.') limit 0;
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--yKkOmjQZXRsvHRX8
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v26-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v27 04/11] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 2 +-
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 76ae2f2dd8..09a5b5b48a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26922,16 +26922,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index f59f853983..a657d8d431 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -709,7 +709,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b85bee2d1e..10ffab88c5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11509,13 +11509,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '8450', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b4112ec298..0d3e88fccf 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -241,8 +241,8 @@ select * from pg_stat_file('.') limit 0;
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 5f7ce3d09f..4183a77b23 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--19uQFt6ulqmgNgg1
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v27-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v28 04/11] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 2 +-
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 507a6d73f8..940c653807 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26950,16 +26950,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index f59f853983..a657d8d431 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -709,7 +709,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 550fbf734a..441cbb2cde 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11526,13 +11526,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '8450', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b4112ec298..0d3e88fccf 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -241,8 +241,8 @@ select * from pg_stat_file('.') limit 0;
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 5f7ce3d09f..4183a77b23 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--vk/v8fjDPiDepTtA
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v28-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v22 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 34c92903d8..4308a50ab2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25849,16 +25849,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 054b8d4b1a..454348a5e3 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -517,7 +517,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 02b1436d83..149dfacf4b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10949,13 +10949,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9147', descr => 'check pages of a relation',
proname => 'pg_relation_check_pages', procost => '10000', prorows => '20',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 20bfe71822..0144f611c2 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--d6Gm4EdcadzBjdND
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v23 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0a924fdcc3..ee23bb763c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25840,16 +25840,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 6f003bbf67..ec016149b6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -517,7 +517,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index add3c7dc62..cffcd03634 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10963,13 +10963,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9979', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 20bfe71822..0144f611c2 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--mhjHhnbe5PrRcwjY
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v23-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v24 05/11] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5e267c4f8d..dcd0aa84d2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25836,16 +25836,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 7351615f8a..da7d4a7c11 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -517,7 +517,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f35a684dcf..e2aea6f48b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10967,13 +10967,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9979', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index fd7c3c791f..7279c83d89 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -228,8 +228,8 @@ select * from pg_stat_file('.') limit 0;
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--mPTHnM80CEnHQ2WJ
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v24-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v19 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2931f9d7d5..24dc90a74d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25813,16 +25813,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return the file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5b0147ee2d..08c7189470 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10894,13 +10894,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9979', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--S0GG+JvAI2G0KxBG
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v19-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 22 +++++++++++---------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 +++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</entry>
<entry><type>setof record</type></entry>
<entry>
- List the name, size, and last modification time of files in the
- temporary directory for <parameter>tablespace</parameter>. If
- <parameter>tablespace</parameter> is not provided, the
- <literal>pg_default</literal> tablespace is used. Access is granted
- to members of the <literal>pg_monitor</literal> role and may be
- granted to other non-superuser roles.
+ For the temporary directory within <parameter>tablespace</parameter>,
+ list each file's name, size, last modification time, and a boolean
+ indicating if it is a directory. If <parameter>tablespace</parameter>
+ is not provided, the <literal>pg_default</literal> tablespace is used.
+ Access is granted to members of the <literal>pg_monitor</literal> role
+ and may be granted to other non-superuser roles.
</entry>
</row>
<row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<primary>pg_ls_tmpdir</primary>
</indexterm>
<para>
- <function>pg_ls_tmpdir</function> returns the name, size, and last modified
- time (mtime) of each file in the temporary file directory for the specified
- <parameter>tablespace</parameter>. If <parameter>tablespace</parameter> is
+ <function>pg_ls_tmpdir</function> lists each file in the temporary file
+ directory for the specified <parameter>tablespace</parameter>, along with
+ its size, last modified time (mtime) and a boolean indicating if the file is a
+ directory. Directories are used for temporary files shared by parallel
+ processes. If <parameter>tablespace</parameter> is
not provided, the <literal>pg_default</literal> tablespace is used. By
default only superusers and members of the <literal>pg_monitor</literal>
role can use this function. Access may be granted to others using
<command>GRANT</command>.
- Filenames beginning with a dot, directories, and other special files are not shown.
+ Filenames beginning with a dot and special file types are not shown.
</para>
<indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument..
@ 2020-03-09 03:57 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Justin Pryzby @ 2020-03-09 03:57 UTC (permalink / raw)
similar to pg_stat_file().
It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.
See following commit which also adds these columns to the other pg_ls_*
functions. Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.
Need catversion bump
---
doc/src/sgml/func.sgml | 17 +++++++++--------
src/backend/utils/adt/genfile.c | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++----
src/test/regress/expected/misc_functions.out | 4 ++--
src/test/regress/output/tablespace.source | 4 ++--
5 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<returnvalue>setof record</returnvalue>
( <parameter>name</parameter> <type>text</type>,
<parameter>size</parameter> <type>bigint</type>,
- <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ <parameter>modification</parameter> <type>timestamp with time zone</type>,
+ <parameter>isdir</parameter> <type>boolean</type> )
</para>
<para>
- Returns the name, size, and last modification time (mtime) of each
- ordinary file in the temporary file directory for the
- specified <parameter>tablespace</parameter>.
- If <parameter>tablespace</parameter> is not provided,
- the <literal>pg_default</literal> tablespace is examined. Filenames
- beginning with a dot, directories, and other special files are
- excluded.
+ For each file in the temporary directory within the given
+ <parameter>tablespace</parameter>, return each file's name, size, last
+ modification time (mtime) and a boolean indicating if the file is a directory.
+ Directories are used for temporary files shared by parallel processes.
+ If <parameter>tablespace</parameter> is not provided, the
+ <literal>pg_default</literal> tablespace is examined.
+ Filenames beginning with a dot and special file types are excluded.
</para>
<para>
This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
* If the directory isn't there, silently return an empty set if missing_ok.
* Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
TempTablespacePath(path, tblspc);
return pg_ls_dir_files(fcinfo, path,
- LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+ LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
- proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+ proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
- proargnames => '{tablespace,name,size,modification}',
+ proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{tablespace,name,size,modification,isdir}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '5032', descr => 'list directory with metadata',
proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR: could not open directory "does not exist": No such file or directory
-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification
-------+------+--------------
+ name | size | modification | isdir
+------+------+--------------+-------
(0 rows)
-- try setting and resetting some properties for the new tablespace
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch"
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-04 16:28 Andrew Dunstan <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-04 16:28 UTC (permalink / raw)
To: Himanshu Upadhyaya <[email protected]>; +Cc: Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 3/2/22 10:19, Andrew Dunstan wrote:
> On 3/1/22 16:41, Andrew Dunstan wrote:
>> On 2/1/22 14:11,I wrote:
>>> 2. The new GUC "sql_json" is a bit of a worry. I understand what it's
>>> trying to do, but I'm trying to convince myself it's not going to be a
>>> fruitful source of error reports, especially if people switch it in the
>>> middle of a session. Maybe it should be an initdb option instead of a GUC?
>>>
>>>
>> So far my efforts have not borne fruit. Here's why:
>>
>>
>> andrew=# set sql_json = jsonb;
>> SET
>> andrew=# create table abc (x text, y json);
>> CREATE TABLE
>> andrew=# \d abc
>> Table "public.abc"
>> Column | Type | Collation | Nullable | Default
>> --------+------+-----------+----------+---------
>> x | text | | |
>> y | json | | |
>>
>> andrew=# insert into abc values ('a','{"q":1}');
>> INSERT 0 1
>> andrew=# select json_each(y) from abc;
>> ERROR: function json_each(json) does not exist
>> LINE 1: select json_each(y) from abc;
>> ^
>> HINT: No function matches the given name and argument types. You might
>> need to add explicit type casts.
>> andrew=# select jsonb_each(y) from abc;
>> jsonb_each
>> ------------
>> (q,1)
>> (1 row)
>>
>>
>> The description tells them the column is json, but the json_* functions
>> don't work on the column and you need to use the jsonb functions. That
>> seems to me a recipe for major confusion. It might be better if we set
>> it at initdb time so it couldn't be changed, but even so it could be
>> horribly confusing.
>>
>> This is certainly severable from the rest of these patches. I'm not sure
>> how severable it is from the SQL/JSON Table patches.
>>
>>
> I have confirmed that this is not required at all for the JSON_TABLE
> patch set.
>
>
> I'll submit new patch sets omitting it shortly. The GUC patch can be
> considered separately, probably as release 16 material, but I think as
> is it's at best quite incomplete.
here's a new set of patches, omitting the GUC patch and with the
beginnings of some message cleanup - there's more work to do there.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
Attachments:
[text/x-patch] 0001-Common-SQL-JSON-clauses-v63.patch (30.8K, ../../[email protected]/2-0001-Common-SQL-JSON-clauses-v63.patch)
download | inline diff:
From 1a7402252e76db3e8f839db5c1020dff05c55b89 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:00:49 -0500
Subject: [PATCH 1/5] Common SQL/JSON clauses
---
src/backend/executor/execExpr.c | 22 ++++
src/backend/nodes/copyfuncs.c | 55 ++++++++
src/backend/nodes/equalfuncs.c | 39 ++++++
src/backend/nodes/makefuncs.c | 54 ++++++++
src/backend/nodes/nodeFuncs.c | 66 ++++++++++
src/backend/nodes/outfuncs.c | 39 ++++++
src/backend/nodes/readfuncs.c | 51 ++++++++
src/backend/optimizer/util/clauses.c | 23 ++++
src/backend/parser/gram.y | 65 +++++++++-
src/backend/parser/parse_expr.c | 181 +++++++++++++++++++++++++++
src/backend/utils/adt/ruleutils.c | 56 +++++++++
src/backend/utils/misc/queryjumble.c | 26 ++++
src/include/nodes/makefuncs.h | 5 +
src/include/nodes/nodes.h | 4 +
src/include/nodes/parsenodes.h | 13 ++
src/include/nodes/primnodes.h | 59 +++++++++
src/include/parser/kwlist.h | 2 +
17 files changed, 758 insertions(+), 2 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 847357bf80..939e92457d 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2428,6 +2428,28 @@ 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;
+ }
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d4f8455a2b..55c36b46a8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2298,6 +2298,52 @@ _copyOnConflictExpr(const OnConflictExpr *from)
return newnode;
}
+
+/*
+ * _copyJsonFormat
+ */
+static JsonFormat *
+_copyJsonFormat(const JsonFormat *from)
+{
+ JsonFormat *newnode = makeNode(JsonFormat);
+
+ COPY_SCALAR_FIELD(format_type);
+ 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;
+}
+
/* ****************************************************************
* pathnodes.h copy functions
*
@@ -5350,6 +5396,15 @@ 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;
/*
* RELATION NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f1002afe7a..0ddebd066e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -841,6 +841,36 @@ _equalOnConflictExpr(const OnConflictExpr *a, const OnConflictExpr *b)
return true;
}
+static bool
+_equalJsonFormat(const JsonFormat *a, const JsonFormat *b)
+{
+ COMPARE_SCALAR_FIELD(format_type);
+ 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;
+}
+
/*
* Stuff from pathnodes.h
*/
@@ -3358,6 +3388,15 @@ 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;
/*
* RELATION NODES
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index c85d8fe975..867a927e7a 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"
@@ -818,3 +819,56 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+/*
+ * makeJsonFormat -
+ * creates a JsonFormat node
+ */
+JsonFormat *
+makeJsonFormat(JsonFormatType type, JsonEncoding encoding, int location)
+{
+ JsonFormat *jf = makeNode(JsonFormat);
+
+ jf->format_type = type;
+ jf->encoding = encoding;
+ jf->location = location;
+
+ return jf;
+}
+
+/*
+ * makeJsonValueExpr -
+ * creates a JsonValueExpr node
+ */
+JsonValueExpr *
+makeJsonValueExpr(Expr *expr, JsonFormat *format)
+{
+ JsonValueExpr *jve = makeNode(JsonValueExpr);
+
+ jve->raw_expr = expr;
+ jve->formatted_expr = NULL;
+ jve->format = format;
+
+ return jve;
+}
+
+/*
+ * makeJsonEncoding -
+ * converts JSON encoding name to enum JsonEncoding
+ */
+JsonEncoding
+makeJsonEncoding(char *name)
+{
+ if (!pg_strcasecmp(name, "utf8"))
+ return JS_ENC_UTF8;
+ if (!pg_strcasecmp(name, "utf16"))
+ return JS_ENC_UTF16;
+ if (!pg_strcasecmp(name, "utf32"))
+ return JS_ENC_UTF32;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized JSON encoding: %s", name)));
+
+ return JS_ENC_DEFAULT;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 47d0564fa2..f081393575 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -250,6 +250,13 @@ 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;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -482,6 +489,8 @@ 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);
default:
break;
}
@@ -956,6 +965,9 @@ 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;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
@@ -1168,6 +1180,10 @@ 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;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1614,6 +1630,9 @@ 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;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2348,6 +2367,16 @@ 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;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -2678,6 +2707,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:
{
@@ -3309,6 +3339,28 @@ 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;
+ }
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -4017,6 +4069,20 @@ 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;
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 6bdad462c7..449d90c8f4 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1751,6 +1751,36 @@ _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_type, 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);
+}
+
/*****************************************************************************
*
* Stuff from pathnodes.h.
@@ -4537,6 +4567,15 @@ 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;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3f68f7c18d..6f398cdc15 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1389,6 +1389,51 @@ _readOnConflictExpr(void)
READ_DONE();
}
+/*
+ * _readJsonFormat
+ */
+static JsonFormat *
+_readJsonFormat(void)
+{
+ READ_LOCALS(JsonFormat);
+
+ READ_ENUM_FIELD(format_type, 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();
+}
+
/*
* Stuff from pathnodes.h.
*
@@ -2974,6 +3019,12 @@ 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
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a707dc9f26..5e2754e789 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -3512,6 +3512,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 a03b33b53b..0192321d9a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -635,6 +635,14 @@ 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_output_clause_opt
+
+%type <ival> json_encoding
+ json_encoding_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
@@ -686,7 +694,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXTENSION EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
- FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
+ FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
@@ -697,7 +705,7 @@ 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
KEY
@@ -781,6 +789,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
@@ -15226,6 +15235,54 @@ opt_asymmetric: ASYMMETRIC
| /*EMPTY*/
;
+/* SQL/JSON support */
+
+json_value_expr:
+ a_expr json_format_clause_opt
+ {
+ $$ = (Node *) makeJsonValueExpr((Expr *) $1, $2);
+ }
+ ;
+
+json_format_clause_opt:
+ FORMAT json_representation
+ {
+ $$ = $2;
+ $$.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_output_clause_opt:
+ RETURNING Typename json_format_clause_opt
+ {
+ JsonOutput *n = makeNode(JsonOutput);
+ n->typeName = $2;
+ n->returning.format = $3;
+ $$ = (Node *) n;
+ }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
/*****************************************************************************
*
@@ -15767,6 +15824,7 @@ unreserved_keyword:
| FIRST_P
| FOLLOWING
| FORCE
+ | FORMAT
| FORWARD
| FUNCTION
| FUNCTIONS
@@ -15798,6 +15856,7 @@ unreserved_keyword:
| INSTEAD
| INVOKER
| ISOLATION
+ | JSON
| KEY
| LABEL
| LANGUAGE
@@ -16314,6 +16373,7 @@ bare_label_keyword:
| FOLLOWING
| FORCE
| FOREIGN
+ | FORMAT
| FORWARD
| FREEZE
| FULL
@@ -16358,6 +16418,7 @@ bare_label_keyword:
| IS
| ISOLATION
| JOIN
+ | JSON
| KEY
| LABEL
| LANGUAGE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 1c09ea24cd..985ddbedf1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -34,6 +34,7 @@
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/date.h"
+#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/timestamp.h"
#include "utils/xml.h"
@@ -3099,3 +3100,183 @@ ParseExprKindName(ParseExprKind exprKind)
}
return "unrecognized expression kind";
}
+
+/*
+ * Make string Const node from JSON encoding name.
+ *
+ * UTF8 is default encoding.
+ */
+static Const *
+getJsonEncodingConst(JsonFormat *format)
+{
+ JsonEncoding encoding;
+ const char *enc;
+ Name encname = palloc(sizeof(NameData));
+
+ if (!format ||
+ format->format_type == JS_FORMAT_DEFAULT ||
+ format->encoding == JS_ENC_DEFAULT)
+ encoding = JS_ENC_UTF8;
+ else
+ encoding = format->encoding;
+
+ switch (encoding)
+ {
+ case JS_ENC_UTF16:
+ enc = "UTF16";
+ break;
+ case JS_ENC_UTF32:
+ enc = "UTF32";
+ break;
+ case JS_ENC_UTF8:
+ enc = "UTF8";
+ break;
+ default:
+ elog(ERROR, "invalid JSON encoding: %d", encoding);
+ break;
+ }
+
+ namestrcpy(encname, enc);
+
+ return makeConst(NAMEOID, -1, InvalidOid, NAMEDATALEN,
+ NameGetDatum(encname), false, false);
+}
+
+/*
+ * Make bytea => text conversion using specified JSON format encoding.
+ */
+static Node *
+makeJsonByteaToTextConversion(Node *expr, JsonFormat *format, int location)
+{
+ Const *encoding = getJsonEncodingConst(format);
+ FuncExpr *fexpr = makeFuncExpr(F_CONVERT_FROM, TEXTOID,
+ list_make2(expr, encoding),
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+
+ fexpr->location = location;
+
+ return (Node *) fexpr;
+}
+
+/*
+ * Make CaseTestExpr node.
+ */
+static Node *
+makeCaseTestExpr(Node *expr)
+{
+ CaseTestExpr *placeholder = makeNode(CaseTestExpr);
+
+ placeholder->typeId = exprType(expr);
+ placeholder->typeMod = exprTypmod(expr);
+ placeholder->collation = exprCollation(expr);
+
+ return (Node *) placeholder;
+}
+
+/*
+ * Transform JSON value expression using specified input JSON format or
+ * default format otherwise.
+ */
+static Node *
+transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
+ JsonFormatType default_format)
+{
+ Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr);
+ Node *rawexpr;
+ JsonFormatType format;
+ Oid exprtype;
+ int location;
+ char typcategory;
+ bool typispreferred;
+
+ 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);
+
+ if (ve->format->format_type != JS_FORMAT_DEFAULT)
+ {
+ if (ve->format->encoding != JS_ENC_DEFAULT && exprtype != BYTEAOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("JSON ENCODING clause is only allowed for bytea input type"),
+ parser_errposition(pstate, ve->format->location)));
+
+ if (exprtype == JSONOID || exprtype == JSONBOID)
+ {
+ format = JS_FORMAT_DEFAULT; /* do not format json[b] types */
+ ereport(WARNING,
+ (errmsg("FORMAT JSON has no effect for json and jsonb types"),
+ parser_errposition(pstate, ve->format->location)));
+ }
+ else
+ format = ve->format->format_type;
+ }
+ else if (exprtype == JSONOID || exprtype == JSONBOID)
+ format = JS_FORMAT_DEFAULT; /* do not format json[b] types */
+ else
+ format = default_format;
+
+ if (format != JS_FORMAT_DEFAULT)
+ {
+ Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
+ Node *orig = makeCaseTestExpr(expr);
+ Node *coerced;
+
+ expr = orig;
+
+ if (exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg(ve->format->format_type == JS_FORMAT_DEFAULT ?
+ "cannot use non-string types with implicit FORMAT JSON clause" :
+ "cannot use non-string types with explicit FORMAT JSON clause"),
+ parser_errposition(pstate, ve->format->location >= 0 ?
+ ve->format->location : location)));
+
+ /* Convert encoded JSON text from bytea. */
+ if (format == JS_FORMAT_JSON && exprtype == BYTEAOID)
+ {
+ expr = makeJsonByteaToTextConversion(expr, ve->format, location);
+ exprtype = TEXTOID;
+ }
+
+ /* Try to coerce to the target type. */
+ coerced = coerce_to_target_type(pstate, expr, exprtype,
+ targettype, -1,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+
+ if (!coerced)
+ {
+ /* If coercion failed, use to_json()/to_jsonb() functions. */
+ Oid fnoid = targettype == JSONOID ? F_TO_JSON : F_TO_JSONB;
+ FuncExpr *fexpr = makeFuncExpr(fnoid, targettype,
+ list_make1(expr),
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+ fexpr->location = location;
+
+ coerced = (Node *) fexpr;
+ }
+
+ if (coerced == orig)
+ expr = rawexpr;
+ else
+ {
+ ve = copyObject(ve);
+ ve->raw_expr = (Expr *) rawexpr;
+ ve->formatted_expr = (Expr *) coerced;
+
+ expr = (Node *) ve;
+ }
+ }
+
+ return expr;
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b16526e65e..f34f5199fa 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8262,6 +8262,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;
}
@@ -8367,6 +8372,48 @@ get_rule_expr_paren(Node *node, deparse_context *context,
appendStringInfoChar(context->buf, ')');
}
+/*
+ * get_json_format - Parse back a JsonFormat node
+ */
+static void
+get_json_format(JsonFormat *format, deparse_context *context)
+{
+ if (format->format_type == JS_FORMAT_DEFAULT)
+ return;
+
+ appendStringInfoString(context->buf,
+ format->format_type == 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(context->buf, " ENCODING %s", encoding);
+ }
+}
+
+/*
+ * get_json_returning - Parse back a JsonReturning structure
+ */
+static void
+get_json_returning(JsonReturning *returning, deparse_context *context,
+ bool json_format_by_default)
+{
+ if (!OidIsValid(returning->typid))
+ return;
+
+ appendStringInfo(context->buf, " RETURNING %s",
+ format_type_with_typemod(returning->typid,
+ returning->typmod));
+
+ if (!json_format_by_default ||
+ returning->format->format_type !=
+ (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON))
+ get_json_format(returning->format, context);
+}
/* ----------
* get_rule_expr - Parse back an expression
@@ -9527,6 +9574,15 @@ 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);
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index a67487e5fe..84435420e4 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -737,6 +737,32 @@ 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_List:
foreach(temp, (List *) node)
{
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 50de4c62af..ec8b71a685 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -106,4 +106,9 @@ 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 JsonEncoding makeJsonEncoding(char *name);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 5d075f0c34..59737f1034 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -201,6 +201,9 @@ typedef enum NodeTag
T_FromExpr,
T_OnConflictExpr,
T_IntoClause,
+ T_JsonFormat,
+ T_JsonReturning,
+ T_JsonValueExpr,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -491,6 +494,7 @@ typedef enum NodeTag
T_VacuumRelation,
T_PublicationObjSpec,
T_PublicationTable,
+ 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 1617702d9d..b9a8f9af76 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1551,6 +1551,19 @@ typedef struct TriggerTransition
bool isTable;
} TriggerTransition;
+/* Nodes for SQL/JSON support */
+
+/*
+ * JsonOutput -
+ * representation of JSON output clause (RETURNING type [FORMAT format])
+ */
+typedef struct JsonOutput
+{
+ NodeTag type;
+ TypeName *typeName; /* RETURNING type name, if specified */
+ JsonReturning returning; /* RETURNING FORMAT clause and type Oids */
+} JsonOutput;
+
/*****************************************************************************
* Raw Grammar Output Statements
*****************************************************************************/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index dab5c4ff5d..118a0d6b49 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1235,6 +1235,65 @@ typedef struct XmlExpr
int location; /* token location, or -1 if unknown */
} XmlExpr;
+/*
+ * JsonEncoding -
+ * representation of JSON ENCODING clause
+ */
+typedef enum JsonEncoding
+{
+ JS_ENC_DEFAULT, /* unspecified */
+ JS_ENC_UTF8,
+ JS_ENC_UTF16,
+ JS_ENC_UTF32,
+} JsonEncoding;
+
+/*
+ * JsonFormatType -
+ * enumeration of JSON formats used in JSON FORMAT clause
+ */
+typedef enum JsonFormatType
+{
+ JS_FORMAT_DEFAULT, /* unspecified */
+ JS_FORMAT_JSON, /* FORMAT JSON [ENCODING ...] */
+ JS_FORMAT_JSONB /* implicit internal format for RETURNING jsonb */
+} JsonFormatType;
+
+/*
+ * JsonFormat -
+ * representation of JSON FORMAT clause
+ */
+typedef struct JsonFormat
+{
+ NodeTag type;
+ JsonFormatType format_type; /* format type */
+ JsonEncoding encoding; /* JSON encoding */
+ int location; /* token location, or -1 if unknown */
+} JsonFormat;
+
+/*
+ * JsonReturning -
+ * transformed representation of JSON RETURNING clause
+ */
+typedef struct JsonReturning
+{
+ NodeTag type;
+ JsonFormat *format; /* output JSON format */
+ Oid typid; /* target type Oid */
+ int32 typmod; /* target type modifier */
+} JsonReturning;
+
+/*
+ * JsonValueExpr -
+ * representation of JSON value expression (expr [FORMAT json_format])
+ */
+typedef struct JsonValueExpr
+{
+ NodeTag type;
+ Expr *raw_expr; /* raw expression */
+ Expr *formatted_expr; /* formatted expression or NULL */
+ JsonFormat *format; /* FORMAT clause, if specified */
+} JsonValueExpr;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index bcef7eed2f..f3502b8be4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -175,6 +175,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,6 +228,7 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL)
PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD, BARE_LABEL)
--
2.25.1
[text/x-patch] 0002-SQL-JSON-constructors-v63.patch (186.8K, ../../[email protected]/3-0002-SQL-JSON-constructors-v63.patch)
download | inline diff:
From f7f9f19fd8d8f77fa9788ae253d9047ac326ae37 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:02:10 -0500
Subject: [PATCH 2/5] SQL/JSON constructors
---
doc/src/sgml/func.sgml | 947 +++++++++++++++++++++++
src/backend/executor/execExpr.c | 63 ++
src/backend/executor/execExprInterp.c | 48 ++
src/backend/jit/llvm/llvmjit_expr.c | 6 +
src/backend/jit/llvm/llvmjit_types.c | 1 +
src/backend/nodes/copyfuncs.c | 173 +++++
src/backend/nodes/equalfuncs.c | 132 ++++
src/backend/nodes/makefuncs.c | 15 +
src/backend/nodes/nodeFuncs.c | 151 ++++
src/backend/nodes/outfuncs.c | 18 +
src/backend/nodes/readfuncs.c | 22 +
src/backend/optimizer/util/clauses.c | 23 +
src/backend/parser/gram.y | 265 ++++++-
src/backend/parser/parse_expr.c | 589 ++++++++++++++
src/backend/parser/parse_target.c | 13 +
src/backend/parser/parser.c | 16 +
src/backend/utils/adt/json.c | 422 +++++++++-
src/backend/utils/adt/jsonb.c | 224 +++++-
src/backend/utils/adt/jsonb_util.c | 24 +-
src/backend/utils/adt/ruleutils.c | 212 ++++-
src/backend/utils/misc/queryjumble.c | 12 +
src/include/catalog/pg_aggregate.dat | 22 +
src/include/catalog/pg_proc.dat | 70 ++
src/include/executor/execExpr.h | 14 +
src/include/nodes/makefuncs.h | 1 +
src/include/nodes/nodes.h | 8 +
src/include/nodes/parsenodes.h | 96 ++-
src/include/nodes/primnodes.h | 25 +
src/include/parser/kwlist.h | 6 +
src/include/utils/json.h | 6 +
src/include/utils/jsonb.h | 9 +
src/interfaces/ecpg/preproc/parse.pl | 2 +
src/interfaces/ecpg/preproc/parser.c | 14 +
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/expected/sqljson.out | 746 ++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/opr_sanity.sql | 6 +-
src/test/regress/sql/sqljson.sql | 282 +++++++
38 files changed, 4561 insertions(+), 130 deletions(-)
create mode 100644 src/test/regress/expected/sqljson.out
create mode 100644 src/test/regress/sql/sqljson.sql
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df3cd5987b..faf0c92f76 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17564,6 +17564,844 @@ $.* ? (@ 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>
+
+ <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 or JSON arrays represented as
+ SQL character or binary strings.
+ </para>
+
+ <itemizedlist>
+ <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-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="sqljson-common-clauses">
+ <title>SQL/JSON Common Clauses</title>
+
+ <sect4 id="sqljson-output-clause">
+ <title>SQL/JSON Output Clause</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable> [ FORMAT JSON [ ENCODING UTF8 ] ]</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">
@@ -19719,6 +20557,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/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 939e92457d..31e628b098 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2450,6 +2450,69 @@ ExecInitExprRec(Expr *node, ExprState *state,
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
+ {
+ 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++;
+ }
+
+ 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;
+
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 d6f7d7c2d7..880cdc8e01 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -71,6 +71,8 @@
#include "utils/date.h"
#include "utils/datum.h"
#include "utils/expandedrecord.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -477,6 +479,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_GROUPING_FUNC,
&&CASE_EEOP_WINDOW_FUNC,
&&CASE_EEOP_SUBPLAN,
+ &&CASE_EEOP_JSON_CONSTRUCTOR,
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
&&CASE_EEOP_AGG_DESERIALIZE,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1786,7 +1789,13 @@ 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();
}
@@ -4386,3 +4395,42 @@ 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_type == 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
+ {
+ res = (Datum) 0;
+ elog(ERROR, "invalid JsonConstructorExpr type %d", ctor->type);
+ }
+
+ *op->resvalue = res;
+ *op->resnull = isnull;
+}
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index bd86f546d7..d0c26cf58b 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2348,6 +2348,12 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_JSON_CONSTRUCTOR:
+ build_EvalXFunc(b, mod, "ExecEvalJsonConstructor",
+ v_state, op, v_econtext);
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ break;
+
case EEOP_LAST:
Assert(false);
break;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index d5191cf02b..53c75dd9d6 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -131,6 +131,7 @@ void *referenced_functions[] =
ExecEvalSysVar,
ExecEvalWholeRowVar,
ExecEvalXmlExpr,
+ ExecEvalJsonConstructor,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 55c36b46a8..d89596a74a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2344,6 +2344,152 @@ _copyJsonValueExpr(const JsonValueExpr *from)
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;
+}
+
/* ****************************************************************
* pathnodes.h copy functions
*
@@ -5405,6 +5551,33 @@ copyObjectImpl(const void *from)
case T_JsonValueExpr:
retval = _copyJsonValueExpr(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;
/*
* RELATION NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0ddebd066e..9cb8495ddf 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -871,6 +871,111 @@ _equalJsonValueExpr(const JsonValueExpr *a, const JsonValueExpr *b)
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;
+}
+
/*
* Stuff from pathnodes.h
*/
@@ -3397,6 +3502,9 @@ equal(const void *a, const void *b)
case T_JsonValueExpr:
retval = _equalJsonValueExpr(a, b);
break;
+ case T_JsonConstructorExpr:
+ retval = _equalJsonConstructorExpr(a, b);
+ break;
/*
* RELATION NODES
@@ -3977,6 +4085,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 867a927e7a..7b4f7972e6 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -872,3 +872,18 @@ makeJsonEncoding(char *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;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index f081393575..120803a841 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -257,6 +257,9 @@ exprType(const Node *expr)
type = exprType((Node *) (jve->formatted_expr ? jve->formatted_expr : jve->raw_expr));
}
break;
+ case T_JsonConstructorExpr:
+ type = ((const JsonConstructorExpr *) expr)->returning->typid;
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -491,6 +494,8 @@ exprTypmod(const Node *expr)
return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
case T_JsonValueExpr:
return exprTypmod((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
+ case T_JsonConstructorExpr:
+ return -1; /* ((const JsonConstructorExpr *) expr)->returning->typmod; */
default:
break;
}
@@ -968,6 +973,16 @@ exprCollation(const Node *expr)
case T_JsonValueExpr:
coll = exprCollation((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
break;
+ case T_JsonConstructorExpr:
+ {
+ const JsonConstructorExpr *ctor = (const JsonConstructorExpr *) expr;
+
+ if (ctor->coercion)
+ coll = exprCollation((Node *) ctor->coercion);
+ else
+ coll = InvalidOid;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
@@ -1184,6 +1199,16 @@ exprSetCollation(Node *expr, Oid collation)
exprSetCollation((Node *) ((JsonValueExpr *) expr)->formatted_expr,
collation);
break;
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *ctor = (JsonConstructorExpr *) expr;
+
+ if (ctor->coercion)
+ exprSetCollation((Node *) ctor->coercion, collation);
+ else
+ Assert(!OidIsValid(collation)); /* result is always a json[b] type */
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1633,6 +1658,9 @@ exprLocation(const Node *expr)
case T_JsonValueExpr:
loc = exprLocation((Node *) ((const JsonValueExpr *) expr)->raw_expr);
break;
+ case T_JsonConstructorExpr:
+ loc = ((const JsonConstructorExpr *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2377,6 +2405,18 @@ expression_tree_walker(Node *node,
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;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -3359,6 +3399,19 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->formatted_expr, jve->formatted_expr, Expr *);
MUTATE(newnode->format, jve->format, JsonFormat *);
+ return (Node *) newnode;
+ }
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *jve = (JsonConstructorExpr *) node;
+ JsonConstructorExpr *newnode;
+
+ FLATCOPY(newnode, jve, JsonConstructorExpr);
+ MUTATE(newnode->args, jve->args, List *);
+ MUTATE(newnode->func, jve->func, Expr *);
+ MUTATE(newnode->coercion, jve->coercion, Expr *);
+ MUTATE(newnode->returning, jve->returning, JsonReturning *);
+
return (Node *) newnode;
}
default:
@@ -4083,6 +4136,104 @@ raw_expression_tree_walker(Node *node,
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;
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 449d90c8f4..c25f0bd684 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1781,6 +1781,21 @@ _outJsonValueExpr(StringInfo str, const JsonValueExpr *node)
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);
+}
+
/*****************************************************************************
*
* Stuff from pathnodes.h.
@@ -4576,6 +4591,9 @@ outNode(StringInfo str, const void *obj)
case T_JsonValueExpr:
_outJsonValueExpr(str, obj);
break;
+ case T_JsonConstructorExpr:
+ _outJsonConstructorExpr(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6f398cdc15..e0b3ad1ed2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1434,6 +1434,26 @@ _readJsonValueExpr(void)
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();
+}
+
/*
* Stuff from pathnodes.h.
*
@@ -3025,6 +3045,8 @@ parseNodeString(void)
return_value = _readJsonReturning();
else if (MATCH("JSONVALUEEXPR", 13))
return_value = _readJsonValueExpr();
+ else if (MATCH("JSONCTOREXPR", 12))
+ return_value = _readJsonConstructorExpr();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 5e2754e789..bac86aad45 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -50,6 +50,8 @@
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
@@ -382,6 +384,27 @@ contain_mutable_functions_walker(Node *node, void *context)
context))
return true;
+ if (IsA(node, JsonConstructorExpr))
+ {
+ const JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+ ListCell *lc;
+ bool is_jsonb =
+ ctor->returning->format->format_type == 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, SQLValueFunction))
{
/* all variants of SQLValueFunction are stable */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0192321d9a..406933da18 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -638,11 +638,31 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> json_format_clause_opt
json_representation
json_value_expr
+ json_func_expr
json_output_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
+
+%type <list> json_name_and_value_list
+ json_value_expr_list
+ json_array_aggregate_order_by_clause_opt
%type <ival> json_encoding
json_encoding_clause_opt
+%type <boolean> json_key_uniqueness_constraint_opt
+ json_object_constructor_null_clause_opt
+ json_array_constructor_null_clause_opt
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
* They must be listed first so that their numeric codes do not depend on
@@ -668,7 +688,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
@@ -705,9 +725,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
- JOIN JSON
+ JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG
- KEY
+ KEY KEYS
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
@@ -771,7 +791,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
@@ -825,11 +845,13 @@ 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 ABSENT UNIQUE
%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 ] */
/* Unary Operators */
%left AT /* sets precedence for AT TIME ZONE */
%left COLLATE
@@ -847,6 +869,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
+
%%
/*
@@ -13372,7 +13397,7 @@ ConstInterval:
opt_timezone:
WITH_LA TIME ZONE { $$ = true; }
- | WITHOUT TIME ZONE { $$ = false; }
+ | WITHOUT_LA TIME ZONE { $$ = false; }
| /*EMPTY*/ { $$ = false; }
;
@@ -13985,6 +14010,17 @@ b_expr: c_expr
}
;
+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.
*
@@ -14237,6 +14273,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; }
;
@@ -14250,6 +14295,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; }
;
/*
@@ -14537,6 +14583,8 @@ func_expr_common_subexpr:
n->location = @1;
$$ = (Node *)n;
}
+ | json_func_expr
+ { $$ = $1; }
;
/*
@@ -15236,11 +15284,14 @@ opt_asymmetric: ASYMMETRIC
;
/* SQL/JSON support */
+json_func_expr:
+ json_value_constructor
+ ;
json_value_expr:
a_expr json_format_clause_opt
{
- $$ = (Node *) makeJsonValueExpr((Expr *) $1, $2);
+ $$ = (Node *) makeJsonValueExpr((Expr *) $1, castNode(JsonFormat, $2));
}
;
@@ -15248,7 +15299,7 @@ json_format_clause_opt:
FORMAT json_representation
{
$$ = $2;
- $$.location = @1;
+ castNode(JsonFormat, $$)->location = @1;
}
| /* EMPTY */
{
@@ -15278,10 +15329,196 @@ json_output_clause_opt:
{
JsonOutput *n = makeNode(JsonOutput);
n->typeName = $2;
- n->returning.format = $3;
+ n->returning = makeNode(JsonReturning);
+ n->returning->format = (JsonFormat *) $3;
$$ = (Node *) n;
}
| /* EMPTY */ { $$ = NULL; }
+ ;
+
+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; }
;
/*****************************************************************************
@@ -15728,6 +15965,7 @@ BareColLabel: IDENT { $$ = $1; }
*/
unreserved_keyword:
ABORT_P
+ | ABSENT
| ABSOLUTE_P
| ACCESS
| ACTION
@@ -15858,6 +16096,7 @@ unreserved_keyword:
| ISOLATION
| JSON
| KEY
+ | KEYS
| LABEL
| LANGUAGE
| LARGE_P
@@ -16066,6 +16305,10 @@ col_name_keyword:
| INT_P
| INTEGER
| INTERVAL
+ | JSON_ARRAY
+ | JSON_ARRAYAGG
+ | JSON_OBJECT
+ | JSON_OBJECTAGG
| LEAST
| NATIONAL
| NCHAR
@@ -16234,6 +16477,7 @@ reserved_keyword:
*/
bare_label_keyword:
ABORT_P
+ | ABSENT
| ABSOLUTE_P
| ACCESS
| ACTION
@@ -16419,7 +16663,12 @@ bare_label_keyword:
| ISOLATION
| JOIN
| JSON
+ | JSON_ARRAY
+ | JSON_ARRAYAGG
+ | JSON_OBJECT
+ | JSON_OBJECTAGG
| KEY
+ | KEYS
| LABEL
| LANGUAGE
| LARGE_P
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 985ddbedf1..6b93a76bca 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"
@@ -75,6 +77,14 @@ static Node *transformWholeRowRef(ParseState *pstate,
static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformJsonObjectConstructor(ParseState *pstate,
+ JsonObjectConstructor *ctor);
+static Node *transformJsonArrayConstructor(ParseState *pstate,
+ JsonArrayConstructor *ctor);
+static Node *transformJsonArrayQueryConstructor(ParseState *pstate,
+ JsonArrayQueryConstructor *ctor);
+static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg);
+static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg);
static Node *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -302,6 +312,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
break;
}
+ case T_JsonObjectConstructor:
+ result = transformJsonObjectConstructor(pstate, (JsonObjectConstructor *) expr);
+ break;
+
+ case T_JsonArrayConstructor:
+ result = transformJsonArrayConstructor(pstate, (JsonArrayConstructor *) expr);
+ break;
+
+ case T_JsonArrayQueryConstructor:
+ result = transformJsonArrayQueryConstructor(pstate, (JsonArrayQueryConstructor *) expr);
+ break;
+
+ case T_JsonObjectAgg:
+ result = transformJsonObjectAgg(pstate, (JsonObjectAgg *) expr);
+ break;
+
+ case T_JsonArrayAgg:
+ result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3280,3 +3310,562 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
return expr;
}
+
+/*
+ * Checks specified output format for its applicability to the target type.
+ */
+static void
+checkJsonOutputFormat(ParseState *pstate, const JsonFormat *format,
+ Oid targettype, bool allow_format_for_non_strings)
+{
+ if (!allow_format_for_non_strings &&
+ format->format_type != JS_FORMAT_DEFAULT &&
+ (targettype != BYTEAOID &&
+ targettype != JSONOID &&
+ targettype != JSONBOID))
+ {
+ char typcategory;
+ bool typispreferred;
+
+ get_type_category_preferred(targettype, &typcategory, &typispreferred);
+
+ if (typcategory != TYPCATEGORY_STRING)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ parser_errposition(pstate, format->location),
+ errmsg("cannot use JSON format with non-string output types")));
+ }
+
+ if (format->format_type == JS_FORMAT_JSON)
+ {
+ JsonEncoding enc = format->encoding != JS_ENC_DEFAULT ?
+ format->encoding : JS_ENC_UTF8;
+
+ if (targettype != BYTEAOID &&
+ format->encoding != JS_ENC_DEFAULT)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ parser_errposition(pstate, format->location),
+ errmsg("cannot set JSON encoding for non-bytea output types")));
+
+ if (enc != JS_ENC_UTF8)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported JSON encoding"),
+ errhint("only UTF8 JSON encoding is supported"),
+ parser_errposition(pstate, format->location)));
+ }
+}
+
+/*
+ * Transform JSON output clause.
+ *
+ * Assigns target type oid and modifier.
+ * Assigns default format or checks specified format for its applicability to
+ * the target type.
+ */
+static JsonReturning *
+transformJsonOutput(ParseState *pstate, const JsonOutput *output,
+ bool allow_format)
+{
+ JsonReturning *ret;
+
+ /* if output clause is not specified, make default clause value */
+ if (!output)
+ {
+ ret = makeNode(JsonReturning);
+
+ ret->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+ ret->typid = InvalidOid;
+ ret->typmod = -1;
+
+ return ret;
+ }
+
+ ret = copyObject(output->returning);
+
+ typenameTypeIdAndMod(pstate, output->typeName, &ret->typid, &ret->typmod);
+
+ if (output->typeName->setof)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning SETOF types is not supported in SQL/JSON functions")));
+
+ if (ret->format->format_type == JS_FORMAT_DEFAULT)
+ /* assign JSONB format when returning jsonb, or JSON format otherwise */
+ ret->format->format_type =
+ ret->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON;
+ else
+ checkJsonOutputFormat(pstate, ret->format, ret->typid, allow_format);
+
+ return ret;
+}
+
+/*
+ * Transform JSON output clause of JSON 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_type = JS_FORMAT_JSONB;
+ }
+ else
+ {
+ /* Note: this includes the have_json case */
+
+ /* XXX TEXT is default by the standard, but we return JSON */
+ returning->typid = JSONOID;
+ returning->format->format_type = JS_FORMAT_JSON;
+ }
+
+ returning->typmod = -1;
+ }
+
+ return returning;
+}
+
+/*
+ * Coerce json[b]-valued function expression to the output type.
+ */
+static Node *
+coerceJsonFuncExpr(ParseState *pstate, Node *expr,
+ const JsonReturning *returning, bool report_error)
+{
+ Node *res;
+ int location;
+ Oid exprtype = exprType(expr);
+
+ /* if output type is not specified or equals to function type, return */
+ if (!OidIsValid(returning->typid) || returning->typid == exprtype)
+ return expr;
+
+ location = exprLocation(expr);
+
+ if (location < 0)
+ location = returning ? returning->format->location : -1;
+
+ /* special case for RETURNING bytea FORMAT json */
+ if (returning->format->format_type == JS_FORMAT_JSON &&
+ returning->typid == BYTEAOID)
+ {
+ /* encode json text into bytea using pg_convert_to() */
+ Node *texpr = coerce_to_specific_type(pstate, expr, TEXTOID,
+ "JSON_FUNCTION");
+ Const *enc = getJsonEncodingConst(returning->format);
+ FuncExpr *fexpr = makeFuncExpr(F_CONVERT_TO, BYTEAOID,
+ list_make2(texpr, enc),
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+ fexpr->location = location;
+
+ return (Node *) fexpr;
+ }
+
+ /* try to coerce expression to the output type */
+ res = coerce_to_target_type(pstate, expr, exprtype,
+ returning->typid, returning->typmod,
+ /* XXX throwing errors when casting to char(N) */
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+
+ if (!res && report_error)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprtype),
+ format_type_be(returning->typid)),
+ parser_coercion_errposition(pstate, location, expr)));
+
+ return res;
+}
+
+static Node *
+makeJsonConstructorExpr(ParseState *pstate, JsonConstructorType type,
+ List *args, Expr *fexpr, JsonReturning *returning,
+ bool unique, bool absent_on_null, int location)
+{
+ JsonConstructorExpr *jsctor = makeNode(JsonConstructorExpr);
+ Node *placeholder;
+ Node *coercion;
+ Oid intermediate_typid =
+ returning->format->format_type == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
+
+ jsctor->args = args;
+ jsctor->func = fexpr;
+ jsctor->type = type;
+ jsctor->returning = returning;
+ jsctor->unique = unique;
+ jsctor->absent_on_null = absent_on_null;
+ jsctor->location = location;
+
+ if (fexpr)
+ placeholder = makeCaseTestExpr((Node *) fexpr);
+ else
+ {
+ CaseTestExpr *cte = makeNode(CaseTestExpr);
+
+ cte->typeId = intermediate_typid;
+ cte->typeMod = -1;
+ cte->collation = InvalidOid;
+
+ placeholder = (Node *) cte;
+ }
+
+ coercion = coerceJsonFuncExpr(pstate, placeholder, returning, true);
+
+ if (coercion != placeholder)
+ jsctor->coercion = (Expr *) coercion;
+
+ return (Node *) jsctor;
+}
+
+/*
+ * Transform JSON_OBJECT() constructor.
+ *
+ * JSON_OBJECT() is transformed into json[b]_build_object[_ext]() call
+ * depending on the output JSON format. The first two arguments of
+ * json[b]_build_object_ext() are absent_on_null and check_key_uniqueness.
+ *
+ * Then function call result is coerced to the target type.
+ */
+static Node *
+transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor)
+{
+ JsonReturning *returning;
+ List *args = NIL;
+
+ /* transform key-value pairs, if any */
+ if (ctor->exprs)
+ {
+ ListCell *lc;
+
+ /* transform and append key-value arguments */
+ foreach(lc, ctor->exprs)
+ {
+ JsonKeyValue *kv = castNode(JsonKeyValue, lfirst(lc));
+ Node *key = transformExprRecurse(pstate, (Node *) kv->key);
+ Node *val = transformJsonValueExpr(pstate, kv->value,
+ JS_FORMAT_DEFAULT);
+
+ args = lappend(args, key);
+ args = lappend(args, val);
+ }
+ }
+
+ returning = transformJsonConstructorOutput(pstate, ctor->output, args);
+
+ return makeJsonConstructorExpr(pstate, JSCTOR_JSON_OBJECT, args, NULL,
+ returning, ctor->unique,
+ ctor->absent_on_null, ctor->location);
+}
+
+/*
+ * Transform JSON_ARRAY(query [FORMAT] [RETURNING] [ON NULL]) into
+ * (SELECT JSON_ARRAYAGG(a [FORMAT] [RETURNING] [ON NULL]) FROM (query) q(a))
+ */
+static Node *
+transformJsonArrayQueryConstructor(ParseState *pstate,
+ JsonArrayQueryConstructor *ctor)
+{
+ SubLink *sublink = makeNode(SubLink);
+ SelectStmt *select = makeNode(SelectStmt);
+ RangeSubselect *range = makeNode(RangeSubselect);
+ Alias *alias = makeNode(Alias);
+ ResTarget *target = makeNode(ResTarget);
+ JsonArrayAgg *agg = makeNode(JsonArrayAgg);
+ ColumnRef *colref = makeNode(ColumnRef);
+ Query *query;
+ ParseState *qpstate;
+
+ /* Transform query only for counting target list entries. */
+ qpstate = make_parsestate(pstate);
+
+ query = transformStmt(qpstate, ctor->query);
+
+ if (count_nonjunk_tlist_entries(query->targetList) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("subquery must return only one column"),
+ parser_errposition(pstate, ctor->location)));
+
+ free_parsestate(qpstate);
+
+ colref->fields = list_make2(makeString(pstrdup("q")),
+ makeString(pstrdup("a")));
+ colref->location = ctor->location;
+
+ agg->arg = makeJsonValueExpr((Expr *) colref, ctor->format);
+ agg->absent_on_null = ctor->absent_on_null;
+ agg->constructor = makeNode(JsonAggConstructor);
+ agg->constructor->agg_order = NIL;
+ agg->constructor->output = ctor->output;
+ agg->constructor->location = ctor->location;
+
+ target->name = NULL;
+ target->indirection = NIL;
+ target->val = (Node *) agg;
+ target->location = ctor->location;
+
+ alias->aliasname = pstrdup("q");
+ alias->colnames = list_make1(makeString(pstrdup("a")));
+
+ range->lateral = false;
+ range->subquery = ctor->query;
+ range->alias = alias;
+
+ select->targetList = list_make1(target);
+ select->fromClause = list_make1(range);
+
+ sublink->subLinkType = EXPR_SUBLINK;
+ sublink->subLinkId = 0;
+ sublink->testexpr = NULL;
+ sublink->operName = NIL;
+ sublink->subselect = (Node *) select;
+ sublink->location = ctor->location;
+
+ return transformExprRecurse(pstate, (Node *) sublink);
+}
+
+/*
+ * Common code for JSON_OBJECTAGG and JSON_ARRAYAGG transformation.
+ */
+static Node *
+transformJsonAggConstructor(ParseState *pstate, JsonAggConstructor *agg_ctor,
+ JsonReturning *returning, List *args,
+ const char *aggfn, Oid aggtype,
+ JsonConstructorType ctor_type,
+ bool unique, bool absent_on_null)
+{
+ Oid aggfnoid;
+ Node *node;
+ Expr *aggfilter = agg_ctor->agg_filter ? (Expr *)
+ transformWhereClause(pstate, agg_ctor->agg_filter,
+ EXPR_KIND_FILTER, "FILTER") : NULL;
+
+ aggfnoid = DatumGetInt32(DirectFunctionCall1(regprocin,
+ CStringGetDatum(aggfn)));
+
+ if (agg_ctor->over)
+ {
+ /* window function */
+ WindowFunc *wfunc = makeNode(WindowFunc);
+
+ wfunc->winfnoid = aggfnoid;
+ wfunc->wintype = aggtype;
+ /* wincollid and inputcollid will be set by parse_collate.c */
+ wfunc->args = args;
+ /* winref will be set by transformWindowFuncCall */
+ wfunc->winstar = false;
+ wfunc->winagg = true;
+ wfunc->aggfilter = aggfilter;
+ wfunc->location = agg_ctor->location;
+
+ /*
+ * ordered aggs not allowed in windows yet
+ */
+ if (agg_ctor->agg_order != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("aggregate ORDER BY is not implemented for window functions"),
+ parser_errposition(pstate, agg_ctor->location)));
+
+ /* parse_agg.c does additional window-func-specific processing */
+ transformWindowFuncCall(pstate, wfunc, agg_ctor->over);
+
+ node = (Node *) wfunc;
+ }
+ else
+ {
+ Aggref *aggref = makeNode(Aggref);
+
+ aggref->aggfnoid = aggfnoid;
+ aggref->aggtype = aggtype;
+
+ /* aggcollid and inputcollid will be set by parse_collate.c */
+ aggref->aggtranstype = InvalidOid; /* will be set by planner */
+ /* aggargtypes will be set by transformAggregateCall */
+ /* aggdirectargs and args will be set by transformAggregateCall */
+ /* aggorder and aggdistinct will be set by transformAggregateCall */
+ aggref->aggfilter = aggfilter;
+ aggref->aggstar = false;
+ aggref->aggvariadic = false;
+ aggref->aggkind = AGGKIND_NORMAL;
+ /* agglevelsup will be set by transformAggregateCall */
+ aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */
+ aggref->location = agg_ctor->location;
+
+ transformAggregateCall(pstate, aggref, args, agg_ctor->agg_order, false);
+
+ node = (Node *) aggref;
+ }
+
+ return makeJsonConstructorExpr(pstate, ctor_type, NIL, (Expr *) node,
+ returning, unique, absent_on_null,
+ agg_ctor->location);
+}
+
+/*
+ * Transform JSON_OBJECTAGG() aggregate function.
+ *
+ * JSON_OBJECTAGG() is transformed into
+ * json[b]_objectagg(key, value, absent_on_null, check_unique) call depending on
+ * the output JSON format. Then the function call result is coerced to the
+ * target output type.
+ */
+static Node *
+transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg)
+{
+ JsonReturning *returning;
+ Node *key;
+ Node *val;
+ List *args;
+ const char *aggfnname;
+ Oid aggtype;
+
+ key = transformExprRecurse(pstate, (Node *) agg->arg->key);
+ val = transformJsonValueExpr(pstate, agg->arg->value, JS_FORMAT_DEFAULT);
+ args = list_make2(key, val);
+
+ returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
+ args);
+
+ if (returning->format->format_type == JS_FORMAT_JSONB)
+ {
+ if (agg->absent_on_null)
+ if (agg->unique)
+ aggfnname = "pg_catalog.jsonb_object_agg_unique_strict"; /* 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 = transformJsonValueExpr(pstate, agg->arg, JS_FORMAT_DEFAULT);
+
+ returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
+ list_make1(arg));
+
+ if (returning->format->format_type == JS_FORMAT_JSONB)
+ {
+ aggfnname = agg->absent_on_null ?
+ "pg_catalog.jsonb_agg_strict" : "pg_catalog.jsonb_agg";
+ aggtype = JSONBOID;
+ }
+ else
+ {
+ aggfnname = agg->absent_on_null ?
+ "pg_catalog.json_agg_strict" : "pg_catalog.json_agg";
+ aggtype = JSONOID;
+ }
+
+ return transformJsonAggConstructor(pstate, agg->constructor, returning,
+ list_make1(arg), aggfnname, aggtype,
+ JSCTOR_JSON_ARRAYAGG,
+ false, agg->absent_on_null);
+}
+
+/*
+ * Transform JSON_ARRAY() constructor.
+ *
+ * JSON_ARRAY() is transformed into json[b]_build_array[_ext]() call
+ * depending on the output JSON format. The first argument of
+ * json[b]_build_array_ext() is absent_on_null.
+ *
+ * Then function call result is coerced to the target type.
+ */
+static Node *
+transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor)
+{
+ JsonReturning *returning;
+ List *args = NIL;
+
+ /* transform element expressions, if any */
+ if (ctor->exprs)
+ {
+ ListCell *lc;
+
+ /* transform and append element arguments */
+ foreach(lc, ctor->exprs)
+ {
+ JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc));
+ Node *val = transformJsonValueExpr(pstate, jsval,
+ JS_FORMAT_DEFAULT);
+
+ args = lappend(args, val);
+ }
+ }
+
+ returning = transformJsonConstructorOutput(pstate, ctor->output, args);
+
+ return makeJsonConstructorExpr(pstate, JSCTOR_JSON_ARRAY, args, NULL,
+ returning, false, ctor->absent_on_null,
+ ctor->location);
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 059eeb9e94..204d285773 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1957,6 +1957,19 @@ FigureColnameInternal(Node *node, char **name)
case T_XmlSerialize:
*name = "xmlserialize";
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;
default:
break;
}
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 50227cc098..eee0a29c08 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/json.c b/src/backend/utils/adt/json.c
index 7879f342e6..d088fafc56 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -13,7 +13,9 @@
*/
#include "postgres.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "common/hashfn.h"
#include "funcapi.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
@@ -42,6 +44,42 @@ typedef enum /* type categories for datum_to_json */
JSONTYPE_OTHER /* all else */
} JsonTypeCategory;
+/* 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
+{
+ 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
{
StringInfo str;
@@ -49,6 +87,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,
@@ -722,6 +761,38 @@ row_to_json_pretty(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(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)
*/
@@ -754,8 +825,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 +866,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 +885,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 +903,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 +945,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 +1081,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 +1112,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
else
{
state = (JsonAggState *) PG_GETARG_POINTER(0);
- appendStringInfoString(state->str, ", ");
}
/*
@@ -925,11 +1127,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 +1183,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 +1236,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 +1262,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 +1283,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 +1344,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 +1378,13 @@ json_build_object_noargs(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
}
-/*
- * SQL function json_build_array(variadic "any")
- */
Datum
-json_build_array(PG_FUNCTION_ARGS)
+json_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types,
+ bool absent_on_null)
{
- int nargs;
int i;
const char *sep = "";
StringInfo result;
- Datum *args;
- bool *nulls;
- Oid *types;
-
- /* fetch argument values to build the array */
- nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls);
-
- if (nargs < 0)
- PG_RETURN_NULL();
result = makeStringInfo();
@@ -1076,6 +1392,9 @@ json_build_array(PG_FUNCTION_ARGS)
for (i = 0; i < nargs; i++)
{
+ if (absent_on_null && nulls[i])
+ continue;
+
appendStringInfoString(result, sep);
sep = ", ";
add_json(args[i], nulls[i], result, types[i], false);
@@ -1083,7 +1402,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));
}
/*
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index f5f40a94bd..a103cbc7c6 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"
@@ -1126,6 +1127,39 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result,
datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar);
}
+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)
*/
@@ -1153,24 +1187,12 @@ to_jsonb(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(JsonbValueToJsonb(result.res));
}
-/*
- * SQL function jsonb_build_object(variadic "any")
- */
Datum
-jsonb_build_object(PG_FUNCTION_ARGS)
+jsonb_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types,
+ bool absent_on_null, bool unique_keys)
{
- int nargs;
int i;
JsonbInState result;
- Datum *args;
- bool *nulls;
- Oid *types;
-
- /* build argument values to build the object */
- nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
-
- if (nargs < 0)
- PG_RETURN_NULL();
if (nargs % 2 != 0)
ereport(ERROR,
@@ -1183,15 +1205,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 +1233,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 +1271,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 +1555,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 +1568,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 +1617,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 +1689,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 +1739,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 +1755,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
*jbval;
JsonbValue v;
JsonbIteratorToken type;
+ bool skip;
if (!AggCheckCallContext(fcinfo, &aggcontext))
{
@@ -1692,6 +1775,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 +1813,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 +1877,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 +1959,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)
{
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 60442758b3..aa151a53d6 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -64,7 +64,8 @@ static int lengthCompareJsonbStringValue(const void *a, const void *b);
static int lengthCompareJsonbString(const char *val1, int len1,
const char *val2, int len2);
static int lengthCompareJsonbPair(const void *a, const void *b, void *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);
@@ -689,7 +690,9 @@ pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq,
appendElement(*pstate, scalarVal);
break;
case WJB_END_OBJECT:
- uniqueifyJsonbObject(&(*pstate)->contVal);
+ uniqueifyJsonbObject(&(*pstate)->contVal,
+ (*pstate)->unique_keys,
+ (*pstate)->skip_nulls);
/* fall through! */
case WJB_END_ARRAY:
/* Steps here common to WJB_END_OBJECT case */
@@ -732,6 +735,9 @@ pushState(JsonbParseState **pstate)
JsonbParseState *ns = palloc(sizeof(JsonbParseState));
ns->next = *pstate;
+ ns->unique_keys = false;
+ ns->skip_nulls = false;
+
return ns;
}
@@ -1936,7 +1942,7 @@ lengthCompareJsonbPair(const void *a, const void *b, void *binequal)
* Sort and unique-ify pairs in JsonbValue object
*/
static void
-uniqueifyJsonbObject(JsonbValue *object)
+uniqueifyJsonbObject(JsonbValue *object, bool unique_keys, bool skip_nulls)
{
bool hasNonUniq = false;
@@ -1946,15 +1952,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/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f34f5199fa..3e5f31ba2e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -457,6 +457,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);
@@ -6245,7 +6251,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, '(');
@@ -8091,6 +8098,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_Aggref:
case T_WindowFunc:
case T_FuncExpr:
+ case T_JsonConstructorExpr:
/* function-like: name(..) or name[..] */
return true;
@@ -8376,12 +8384,12 @@ get_rule_expr_paren(Node *node, deparse_context *context,
* get_json_format - Parse back a JsonFormat node
*/
static void
-get_json_format(JsonFormat *format, deparse_context *context)
+get_json_format(JsonFormat *format, StringInfo buf)
{
if (format->format_type == JS_FORMAT_DEFAULT)
return;
- appendStringInfoString(context->buf,
+ appendStringInfoString(buf,
format->format_type == JS_FORMAT_JSONB ?
" FORMAT JSONB" : " FORMAT JSON");
@@ -8391,7 +8399,7 @@ get_json_format(JsonFormat *format, deparse_context *context)
format->encoding == JS_ENC_UTF16 ? "UTF16" :
format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8";
- appendStringInfo(context->buf, " ENCODING %s", encoding);
+ appendStringInfo(buf, " ENCODING %s", encoding);
}
}
@@ -8399,20 +8407,20 @@ get_json_format(JsonFormat *format, deparse_context *context)
* get_json_returning - Parse back a JsonReturning structure
*/
static void
-get_json_returning(JsonReturning *returning, deparse_context *context,
+get_json_returning(JsonReturning *returning, StringInfo buf,
bool json_format_by_default)
{
if (!OidIsValid(returning->typid))
return;
- appendStringInfo(context->buf, " RETURNING %s",
+ appendStringInfo(buf, " RETURNING %s",
format_type_with_typemod(returning->typid,
returning->typmod));
if (!json_format_by_default ||
returning->format->format_type !=
(returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON))
- get_json_format(returning->format, context);
+ get_json_format(returning->format, buf);
}
/* ----------
@@ -9579,10 +9587,14 @@ get_rule_expr(Node *node, deparse_context *context,
JsonValueExpr *jve = (JsonValueExpr *) node;
get_rule_expr((Node *) jve->raw_expr, context, false);
- get_json_format(jve->format, context);
+ get_json_format(jve->format, context->buf);
}
break;
+ case T_JsonConstructorExpr:
+ get_json_constructor((JsonConstructorExpr *) node, context, false);
+ break;
+
case T_List:
{
char *sep;
@@ -9851,17 +9863,89 @@ 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");
+
+ 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_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
@@ -9891,13 +9975,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))
@@ -9933,7 +10018,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);
@@ -9947,6 +10042,9 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
}
}
+ if (options)
+ appendStringInfoString(buf, options);
+
if (aggref->aggfilter != NULL)
{
appendStringInfoString(buf, ") FILTER (WHERE ");
@@ -9956,6 +10054,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
@@ -9975,10 +10083,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];
@@ -10002,16 +10112,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)
{
@@ -10048,6 +10172,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
*
@@ -10288,6 +10421,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/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index 84435420e4..d14b751058 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -763,6 +763,18 @@ JumbleExpr(JumbleState *jstate, Node *node)
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_List:
foreach(temp, (List *) node)
{
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 2843f4b415..1934f19335 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -567,14 +567,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 bf88858171..1d8b38fdcc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8737,6 +8737,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' },
@@ -8744,10 +8748,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',
@@ -8756,6 +8776,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',
@@ -9628,6 +9661,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',
@@ -9636,10 +9673,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',
@@ -9648,6 +9704,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 56a89ebafb..c830fcf726 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -239,6 +239,7 @@ typedef enum ExprEvalOp
EEOP_GROUPING_FUNC,
EEOP_WINDOW_FUNC,
EEOP_SUBPLAN,
+ EEOP_JSON_CONSTRUCTOR,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -668,6 +669,17 @@ 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;
+ int nargs;
+ } json_constructor;
+
} d;
} ExprEvalStep;
@@ -769,6 +781,8 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
+extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index ec8b71a685..e50b933288 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -109,6 +109,7 @@ extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_
extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding,
int location);
extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format);
+extern Node *makeJsonKeyValue(Node *key, Node *value);
extern JsonEncoding makeJsonEncoding(char *name);
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 59737f1034..05f0b79e82 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -204,6 +204,7 @@ typedef enum NodeTag
T_JsonFormat,
T_JsonReturning,
T_JsonValueExpr,
+ T_JsonConstructorExpr,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -494,6 +495,13 @@ typedef enum NodeTag
T_VacuumRelation,
T_PublicationObjSpec,
T_PublicationTable,
+ T_JsonObjectConstructor,
+ T_JsonArrayConstructor,
+ T_JsonArrayQueryConstructor,
+ T_JsonAggConstructor,
+ T_JsonObjectAgg,
+ T_JsonArrayAgg,
+ T_JsonKeyValue,
T_JsonOutput,
/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b9a8f9af76..40fb034028 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1561,9 +1561,103 @@ typedef struct JsonOutput
{
NodeTag type;
TypeName *typeName; /* RETURNING type name, if specified */
- JsonReturning returning; /* RETURNING FORMAT clause and type Oids */
+ JsonReturning *returning; /* RETURNING FORMAT clause and type Oids */
} JsonOutput;
+/*
+ * JsonKeyValue -
+ * untransformed representation of JSON object key-value pair for
+ * JSON_OBJECT() and JSON_OBJECTAGG()
+ */
+typedef struct JsonKeyValue
+{
+ NodeTag type;
+ Expr *key; /* key expression */
+ JsonValueExpr *value; /* JSON value expression */
+} JsonKeyValue;
+
+/*
+ * JsonObjectConstructor -
+ * untransformed representation of JSON_OBJECT() constructor
+ */
+typedef struct JsonObjectConstructor
+{
+ NodeTag type;
+ List *exprs; /* list of JsonKeyValue pairs */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ bool absent_on_null; /* skip NULL values? */
+ bool unique; /* check key uniqueness? */
+ int location; /* token location, or -1 if unknown */
+} JsonObjectConstructor;
+
+/*
+ * JsonArrayConstructor -
+ * untransformed representation of JSON_ARRAY(element,...) constructor
+ */
+typedef struct JsonArrayConstructor
+{
+ NodeTag type;
+ List *exprs; /* list of JsonValueExpr elements */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ bool absent_on_null; /* skip NULL elements? */
+ int location; /* token location, or -1 if unknown */
+} JsonArrayConstructor;
+
+/*
+ * JsonArrayQueryConstructor -
+ * untransformed representation of JSON_ARRAY(subquery) constructor
+ */
+typedef struct JsonArrayQueryConstructor
+{
+ NodeTag type;
+ Node *query; /* subquery */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ JsonFormat *format; /* FORMAT clause for subquery, if specified */
+ bool absent_on_null; /* skip NULL elements? */
+ int location; /* token location, or -1 if unknown */
+} JsonArrayQueryConstructor;
+
+/*
+ * JsonAggConstructor -
+ * common fields of untransformed representation of
+ * JSON_ARRAYAGG() and JSON_OBJECTAGG()
+ */
+typedef struct JsonAggConstructor
+{
+ NodeTag type;
+ JsonOutput *output; /* RETURNING clause, if any */
+ Node *agg_filter; /* FILTER clause, if any */
+ List *agg_order; /* ORDER BY clause, if any */
+ struct WindowDef *over; /* OVER clause, if any */
+ int location; /* token location, or -1 if unknown */
+} JsonAggConstructor;
+
+/*
+ * JsonObjectAgg -
+ * untransformed representation of JSON_OBJECTAGG()
+ */
+typedef struct JsonObjectAgg
+{
+ NodeTag type;
+ JsonAggConstructor *constructor; /* common fields */
+ JsonKeyValue *arg; /* object key-value pair */
+ bool absent_on_null; /* skip NULL values? */
+ bool unique; /* check key uniqueness? */
+} JsonObjectAgg;
+
+/*
+ * JsonArrayAgg -
+ * untransformed representation of JSON_ARRRAYAGG()
+ */
+typedef struct JsonArrayAgg
+{
+ NodeTag type;
+ JsonAggConstructor *constructor; /* common fields */
+ JsonValueExpr *arg; /* array element expression */
+ bool absent_on_null; /* skip NULL elements? */
+} JsonArrayAgg;
+
+
/*****************************************************************************
* Raw Grammar Output Statements
*****************************************************************************/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 118a0d6b49..20e101e8bd 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1294,6 +1294,31 @@ typedef struct JsonValueExpr
JsonFormat *format; /* FORMAT clause, if specified */
} JsonValueExpr;
+typedef enum JsonConstructorType
+{
+ JSCTOR_JSON_OBJECT = 1,
+ JSCTOR_JSON_ARRAY = 2,
+ JSCTOR_JSON_OBJECTAGG = 3,
+ JSCTOR_JSON_ARRAYAGG = 4
+} JsonConstructorType;
+
+/*
+ * JsonConstructorExpr -
+ * wrapper over FuncExpr/Aggref/WindowFunc for SQL/JSON constructors
+ */
+typedef struct JsonConstructorExpr
+{
+ Expr xpr;
+ JsonConstructorType type; /* constructor type */
+ List *args;
+ Expr *func; /* underlying json[b]_xxx() function call */
+ Expr *coercion; /* coercion to RETURNING type */
+ JsonReturning *returning; /* RETURNING clause */
+ bool absent_on_null; /* ABSENT ON NULL? */
+ bool unique; /* WITH UNIQUE KEYS? (JSON_OBJECT[AGG] only) */
+ int location;
+} JsonConstructorExpr;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f3502b8be4..f44440d4a9 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)
@@ -229,7 +230,12 @@ PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL)
PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index 8a84a0cdb4..63d83b815f 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -20,5 +20,11 @@
extern void escape_json(StringInfo buf, const char *str);
extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
const int *tzp);
+extern bool to_json_is_immutable(Oid typoid);
+extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null,
+ bool unique_keys);
+extern Datum json_build_array_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null);
#endif /* JSON_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 4cbe6edf21..6bcf35dd0a 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;
/*
@@ -412,4 +414,11 @@ extern Datum jsonb_set_element(Jsonb *jb, Datum *path, int path_len,
JsonbValue *newval);
extern Datum jsonb_get_element(Jsonb *jb, Datum *path, int npath,
bool *isnull, bool as_text);
+extern bool to_jsonb_is_immutable(Oid typoid);
+extern Datum jsonb_build_object_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null,
+ bool unique_keys);
+extern Datum jsonb_build_array_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null);
+
#endif /* __JSONB_H__ */
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index dee6b8200d..5ec511fd01 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 a44e07a17a..5e2b606f9b 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/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 4ce6c039b4..15e4016836 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..7dca5a8a30
--- /dev/null
+++ b/src/test/regress/expected/sqljson.out
@@ -0,0 +1,746 @@
+-- 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;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 6d8f524ae9..3ce701a588 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
# ----------
# Another group of parallel tests (JSON related)
# ----------
-test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath
+test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 2b292851e3..63fe114fed 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..aaef2d8aab
--- /dev/null
+++ b/src/test/regress/sql/sqljson.sql
@@ -0,0 +1,282 @@
+-- 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;
--
2.25.1
[text/x-patch] 0003-IS-JSON-predicate-v63.patch (54.5K, ../../[email protected]/4-0003-IS-JSON-predicate-v63.patch)
download | inline diff:
From fd7c3c6ad4dc8e85fad9b3a57059c24a5387cf3e Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:02:53 -0500
Subject: [PATCH 3/5] IS JSON predicate
---
doc/src/sgml/func.sgml | 251 +++++++++++++++++++++++++-
src/backend/executor/execExpr.c | 13 ++
src/backend/executor/execExprInterp.c | 95 ++++++++++
src/backend/jit/llvm/llvmjit_expr.c | 6 +
src/backend/jit/llvm/llvmjit_types.c | 1 +
src/backend/nodes/copyfuncs.c | 20 ++
src/backend/nodes/equalfuncs.c | 15 ++
src/backend/nodes/makefuncs.c | 19 ++
src/backend/nodes/nodeFuncs.c | 26 +++
src/backend/nodes/outfuncs.c | 14 ++
src/backend/nodes/readfuncs.c | 18 ++
src/backend/parser/gram.y | 63 ++++++-
src/backend/parser/parse_expr.c | 76 ++++++++
src/backend/utils/adt/json.c | 105 +++++++++--
src/backend/utils/adt/jsonfuncs.c | 20 ++
src/backend/utils/adt/ruleutils.c | 35 ++++
src/backend/utils/misc/queryjumble.c | 10 +
src/include/executor/execExpr.h | 8 +
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/primnodes.h | 26 +++
src/include/parser/kwlist.h | 1 +
src/include/utils/json.h | 1 +
src/include/utils/jsonfuncs.h | 3 +
src/test/regress/expected/sqljson.out | 198 ++++++++++++++++++++
src/test/regress/sql/sqljson.sql | 96 ++++++++++
26 files changed, 1106 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index faf0c92f76..7a35a44075 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17606,7 +17606,16 @@ $.* ? (@ like_regex "^\\d+$")
</listitem>
</itemizedlist>
- <sect3 id="functions-sqljson-producing">
+ <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>
@@ -18366,10 +18375,250 @@ FROM films AS f;
</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>
+ </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-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="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>
+[ PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } [, ...]]</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>
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 31e628b098..95a3787130 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2513,6 +2513,19 @@ ExecInitExprRec(Expr *node, ExprState *state,
}
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;
+ }
+
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 880cdc8e01..438d482b1c 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -73,6 +73,7 @@
#include "utils/expandedrecord.h"
#include "utils/json.h"
#include "utils/jsonb.h"
+#include "utils/jsonfuncs.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -480,6 +481,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_WINDOW_FUNC,
&&CASE_EEOP_SUBPLAN,
&&CASE_EEOP_JSON_CONSTRUCTOR,
+ &&CASE_EEOP_IS_JSON,
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
&&CASE_EEOP_AGG_DESERIALIZE,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1799,6 +1801,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_IS_JSON)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalJsonIsPredicate(state, op);
+
+ EEO_NEXT();
+ }
+
EEO_CASE(EEOP_LAST)
{
/* unreachable */
@@ -3909,6 +3919,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);
+ }
+ 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
*
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index d0c26cf58b..02511c6aec 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2354,6 +2354,12 @@ llvm_compile_expr(ExprState *state)
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_LAST:
Assert(false);
break;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 53c75dd9d6..4d7029a27f 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -132,6 +132,7 @@ void *referenced_functions[] =
ExecEvalWholeRowVar,
ExecEvalXmlExpr,
ExecEvalJsonConstructor,
+ ExecEvalJsonIsPredicate,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d89596a74a..ce3102a452 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2490,6 +2490,23 @@ _copyJsonArrayQueryConstructor(const JsonArrayQueryConstructor *from)
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;
+}
+
/* ****************************************************************
* pathnodes.h copy functions
*
@@ -5578,6 +5595,9 @@ copyObjectImpl(const void *from)
case T_JsonArrayAgg:
retval = _copyJsonArrayAgg(from);
break;
+ case T_JsonIsPredicate:
+ retval = _copyJsonIsPredicate(from);
+ break;
/*
* RELATION NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 9cb8495ddf..0fda7680b3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -976,6 +976,18 @@ _equalJsonArrayQueryConstructor(const JsonArrayQueryConstructor *a,
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;
+}
+
/*
* Stuff from pathnodes.h
*/
@@ -3505,6 +3517,9 @@ equal(const void *a, const void *b)
case T_JsonConstructorExpr:
retval = _equalJsonConstructorExpr(a, b);
break;
+ case T_JsonIsPredicate:
+ retval = _equalJsonIsPredicate(a, b);
+ break;
/*
* RELATION NODES
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 7b4f7972e6..b67e7c5297 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -887,3 +887,22 @@ makeJsonKeyValue(Node *key, Node *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 120803a841..191abcf32f 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -260,6 +260,9 @@ exprType(const Node *expr)
case T_JsonConstructorExpr:
type = ((const JsonConstructorExpr *) expr)->returning->typid;
break;
+ case T_JsonIsPredicate:
+ type = BOOLOID;
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -983,6 +986,9 @@ exprCollation(const Node *expr)
coll = InvalidOid;
}
break;
+ case T_JsonIsPredicate:
+ coll = InvalidOid; /* result is always an boolean type */
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
@@ -1209,6 +1215,9 @@ exprSetCollation(Node *expr, Oid collation)
Assert(!OidIsValid(collation)); /* result is always a json[b] type */
}
break;
+ case T_JsonIsPredicate:
+ Assert(!OidIsValid(collation)); /* result is always boolean */
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1661,6 +1670,9 @@ exprLocation(const Node *expr)
case T_JsonConstructorExpr:
loc = ((const JsonConstructorExpr *) expr)->location;
break;
+ case T_JsonIsPredicate:
+ loc = ((const JsonIsPredicate *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2417,6 +2429,8 @@ expression_tree_walker(Node *node,
return true;
}
break;
+ case T_JsonIsPredicate:
+ return walker(((JsonIsPredicate *) node)->expr, context);
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -3412,6 +3426,16 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->coercion, jve->coercion, Expr *);
MUTATE(newnode->returning, jve->returning, JsonReturning *);
+ return (Node *) newnode;
+ }
+ case T_JsonIsPredicate:
+ {
+ JsonIsPredicate *pred = (JsonIsPredicate *) node;
+ JsonIsPredicate *newnode;
+
+ FLATCOPY(newnode, pred, JsonIsPredicate);
+ MUTATE(newnode->expr, pred->expr, Node *);
+
return (Node *) newnode;
}
default:
@@ -4234,6 +4258,8 @@ raw_expression_tree_walker(Node *node,
return true;
}
break;
+ case T_JsonIsPredicate:
+ return walker(((JsonIsPredicate *) node)->expr, context);
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 c25f0bd684..ed5d159f6c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1796,6 +1796,17 @@ _outJsonConstructorExpr(StringInfo str, const JsonConstructorExpr *node)
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);
+}
+
/*****************************************************************************
*
* Stuff from pathnodes.h.
@@ -4594,6 +4605,9 @@ outNode(StringInfo str, const void *obj)
case T_JsonConstructorExpr:
_outJsonConstructorExpr(str, obj);
break;
+ case T_JsonIsPredicate:
+ _outJsonIsPredicate(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e0b3ad1ed2..aaf947b6cb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1454,6 +1454,22 @@ _readJsonConstructorExpr(void)
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.
*
@@ -3047,6 +3063,8 @@ parseNodeString(void)
return_value = _readJsonValueExpr();
else if (MATCH("JSONCTOREXPR", 12))
return_value = _readJsonConstructorExpr();
+ else if (MATCH("JSONISPREDICATE", 15))
+ return_value = _readJsonIsPredicate();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 406933da18..1d589e9b41 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -658,6 +658,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> json_encoding
json_encoding_clause_opt
+ json_predicate_type_constraint_opt
%type <boolean> json_key_uniqueness_constraint_opt
json_object_constructor_null_clause_opt
@@ -727,7 +728,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG
- KEY KEYS
+ KEY KEYS KEEP
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
@@ -755,9 +756,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
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
+ 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 STRIP_P
SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
@@ -845,13 +846,14 @@ 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 ABSENT UNIQUE
+%nonassoc ABSENT 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
@@ -13928,6 +13930,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
{
/*
@@ -14010,6 +14052,14 @@ 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; }
@@ -16197,6 +16247,7 @@ unreserved_keyword:
| ROWS
| RULE
| SAVEPOINT
+ | SCALAR
| SCHEMA
| SCHEMAS
| SCROLL
@@ -16667,6 +16718,7 @@ bare_label_keyword:
| JSON_ARRAYAGG
| JSON_OBJECT
| JSON_OBJECTAGG
+ | KEEP
| KEY
| KEYS
| LABEL
@@ -16794,6 +16846,7 @@ bare_label_keyword:
| ROWS
| RULE
| SAVEPOINT
+ | SCALAR
| SCHEMA
| SCHEMAS
| SCROLL
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6b93a76bca..0e504096fd 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -85,6 +85,7 @@ 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 *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -332,6 +333,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr);
break;
+ case T_JsonIsPredicate:
+ result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3869,3 +3874,74 @@ transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor)
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);
+}
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index d088fafc56..5edcb8bb60 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -13,6 +13,7 @@
*/
#include "postgres.h"
+#include "access/hash.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
@@ -1655,6 +1656,94 @@ 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)
+{
+ 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)
+ return false; /* invalid json */
+
+ if (check_unique_keys && !state.unique)
+ return false; /* not unique keys */
+
+ return true; /* ok */
+}
+
/*
* SQL function json_typeof(json) -> text
*
@@ -1670,21 +1759,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/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 2457061f97..03e7f10cd2 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -5647,3 +5647,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/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3e5f31ba2e..be8a75d8a7 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8192,6 +8192,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:
@@ -9595,6 +9596,40 @@ get_rule_expr(Node *node, deparse_context *context,
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_List:
{
char *sep;
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index d14b751058..8315812793 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -775,6 +775,16 @@ JumbleExpr(JumbleState *jstate, Node *node)
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_List:
foreach(temp, (List *) node)
{
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index c830fcf726..a41722ae1e 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -240,6 +240,7 @@ typedef enum ExprEvalOp
EEOP_WINDOW_FUNC,
EEOP_SUBPLAN,
EEOP_JSON_CONSTRUCTOR,
+ EEOP_IS_JSON,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -680,6 +681,12 @@ typedef struct ExprEvalStep
int nargs;
} json_constructor;
+ /* for EEOP_IS_JSON */
+ struct
+ {
+ JsonIsPredicate *pred; /* original expression node */
+ } is_json;
+
} d;
} ExprEvalStep;
@@ -774,6 +781,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);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index e50b933288..380940968b 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -110,6 +110,9 @@ extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding,
int location);
extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format);
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 05f0b79e82..666b45c5da 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -501,6 +501,7 @@ typedef enum NodeTag
T_JsonAggConstructor,
T_JsonObjectAgg,
T_JsonArrayAgg,
+ T_JsonIsPredicate,
T_JsonKeyValue,
T_JsonOutput,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 20e101e8bd..3c4c248e29 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1319,6 +1319,32 @@ typedef struct JsonConstructorExpr
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;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f44440d4a9..1726d73da6 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -372,6 +372,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)
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index 63d83b815f..bfe5b21591 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -26,5 +26,6 @@ extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls,
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);
#endif /* JSON_H */
diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h
index 865b2ff7c1..cd16b6c0c8 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);
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 7dca5a8a30..27dca7815a 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -744,3 +744,201 @@ CREATE OR REPLACE VIEW public.json_array_subquery_view AS
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/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
index aaef2d8aab..4f3c06dcb3 100644
--- a/src/test/regress/sql/sqljson.sql
+++ b/src/test/regress/sql/sqljson.sql
@@ -280,3 +280,99 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING
\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.1
[text/x-patch] 0004-SQL-JSON-query-functions-v63.patch (195.7K, ../../[email protected]/5-0004-SQL-JSON-query-functions-v63.patch)
download | inline diff:
From f5e8ad6b35870c8599482c0e5911360e75676769 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:11:14 -0500
Subject: [PATCH 4/5] SQL/JSON query functions
---
doc/src/sgml/func.sgml | 516 +++++++++-
src/backend/executor/execExpr.c | 206 +++-
src/backend/executor/execExprInterp.c | 543 ++++++++++
src/backend/jit/llvm/llvmjit_expr.c | 6 +
src/backend/jit/llvm/llvmjit_types.c | 1 +
src/backend/nodes/copyfuncs.c | 150 +++
src/backend/nodes/equalfuncs.c | 82 ++
src/backend/nodes/makefuncs.c | 15 +
src/backend/nodes/nodeFuncs.c | 186 +++-
src/backend/nodes/outfuncs.c | 70 ++
src/backend/nodes/readfuncs.c | 86 ++
src/backend/optimizer/path/costsize.c | 3 +-
src/backend/optimizer/util/clauses.c | 32 +
src/backend/parser/gram.y | 333 +++++-
src/backend/parser/parse_collate.c | 4 +
src/backend/parser/parse_expr.c | 490 ++++++++-
src/backend/parser/parse_target.c | 15 +
src/backend/utils/adt/formatting.c | 45 +-
src/backend/utils/adt/jsonb.c | 62 ++
src/backend/utils/adt/jsonfuncs.c | 50 +-
src/backend/utils/adt/jsonpath.c | 257 +++++
src/backend/utils/adt/jsonpath_exec.c | 350 ++++++-
src/backend/utils/adt/ruleutils.c | 135 +++
src/backend/utils/misc/queryjumble.c | 21 +
src/include/executor/execExpr.h | 54 +
src/include/executor/executor.h | 2 +
src/include/nodes/makefuncs.h | 1 +
src/include/nodes/nodes.h | 8 +
src/include/nodes/parsenodes.h | 59 ++
src/include/nodes/primnodes.h | 109 ++
src/include/parser/kwlist.h | 11 +
src/include/utils/formatting.h | 4 +
src/include/utils/jsonb.h | 3 +
src/include/utils/jsonfuncs.h | 4 +
src/include/utils/jsonpath.h | 33 +
src/test/regress/expected/json_sqljson.out | 15 +
src/test/regress/expected/jsonb_sqljson.out | 1018 +++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/json_sqljson.sql | 11 +
src/test/regress/sql/jsonb_sqljson.sql | 317 ++++++
40 files changed, 5185 insertions(+), 124 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/sql/json_sqljson.sql
create mode 100644 src/test/regress/sql/jsonb_sqljson.sql
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7a35a44075..decca95b1b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18394,6 +18394,21 @@ FROM films AS f;
<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>
@@ -18423,6 +18438,501 @@ INSERT INTO my_films VALUES (
</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>
@@ -18582,7 +19092,7 @@ FROM
<varlistentry>
<term>
<literal><replaceable>context_item</replaceable>, <replaceable>path_expression</replaceable>
-[ PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } [, ...]]</literal>
+<optional> PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } <optional>, ...</optional></optional></literal>
</term>
<listitem>
<para>
@@ -18611,7 +19121,7 @@ FROM
</listitem>
</itemizedlist>
<para>
- The input clause is common for all SQL/JSON query functions.
+ The input clause is common for all SQL/JSON query functions.
</para>
</listitem>
</varlistentry>
@@ -18625,7 +19135,7 @@ FROM
<variablelist>
<varlistentry>
<term>
- <literal>RETURNING <replaceable class="parameter">data_type</replaceable> [ FORMAT JSON [ ENCODING UTF8 ] ]</literal>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional></literal>
</term>
<listitem>
<para>
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 95a3787130..f6ec6fa846 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -47,6 +47,7 @@
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/datum.h"
+#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
@@ -85,6 +86,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 +157,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 +169,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);
}
/*
@@ -2526,6 +2524,112 @@ ExecInitExprRec(Expr *node, ExprState *state,
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->sval);
+ 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 438d482b1c..bd90b02be1 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -57,14 +57,18 @@
#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"
@@ -74,8 +78,10 @@
#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"
@@ -482,6 +488,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&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,
@@ -1805,7 +1812,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
{
/* 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();
}
@@ -4529,3 +4542,533 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
*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 02511c6aec..9c8f341d96 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2360,6 +2360,12 @@ llvm_compile_expr(ExprState *state)
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 4d7029a27f..b2bda86889 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -133,6 +133,7 @@ void *referenced_functions[] =
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 ce3102a452..e5c9f403e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2490,6 +2490,90 @@ _copyJsonArrayQueryConstructor(const JsonArrayQueryConstructor *from)
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
*/
@@ -2507,6 +2591,51 @@ _copyJsonIsPredicate(const JsonIsPredicate *from)
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
*
@@ -5598,6 +5727,27 @@ copyObjectImpl(const void *from)
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 0fda7680b3..8a60e66970 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -988,6 +988,76 @@ _equalJsonIsPredicate(const JsonIsPredicate *a,
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
*/
@@ -3520,6 +3590,18 @@ equal(const void *a, const void *b)
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
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index b67e7c5297..cd6c300e7b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -852,6 +852,21 @@ makeJsonValueExpr(Expr *expr, JsonFormat *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
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 191abcf32f..e8de1dd3aa 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -263,6 +263,12 @@ exprType(const Node *expr)
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 */
@@ -498,7 +504,11 @@ exprTypmod(const Node *expr)
case T_JsonValueExpr:
return exprTypmod((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
case T_JsonConstructorExpr:
- return -1; /* ((const JsonConstructorExpr *) expr)->returning->typmod; */
+ 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;
}
@@ -989,6 +999,21 @@ exprCollation(const Node *expr)
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 */
@@ -1218,6 +1243,21 @@ exprSetCollation(Node *expr, Oid collation)
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;
@@ -1673,6 +1713,15 @@ exprLocation(const Node *expr)
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;
@@ -2431,6 +2480,54 @@ expression_tree_walker(Node *node,
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));
@@ -3428,6 +3525,7 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
+ break;
case T_JsonIsPredicate:
{
JsonIsPredicate *pred = (JsonIsPredicate *) node;
@@ -3438,6 +3536,55 @@ expression_tree_mutator(Node *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));
@@ -4260,6 +4407,43 @@ raw_expression_tree_walker(Node *node,
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 ed5d159f6c..23750d97ab 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1807,6 +1807,64 @@ _outJsonIsPredicate(StringInfo str, const JsonIsPredicate *node)
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.
@@ -4608,6 +4666,18 @@ outNode(StringInfo str, const void *obj)
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 aaf947b6cb..c15e81a362 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1454,6 +1454,84 @@ _readJsonConstructorExpr(void)
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
*/
@@ -3065,6 +3143,14 @@ parseNodeString(void)
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 8dc7dd4ca2..c7b9d8d11a 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 bac86aad45..eef4381477 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"
@@ -52,6 +53,7 @@
#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"
@@ -405,6 +407,24 @@ contain_mutable_functions_walker(Node *node, void *context)
/* 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 */
@@ -876,6 +896,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,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1d589e9b41..ff680b61d8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -280,6 +280,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct GroupClause *groupclause;
struct KeyActions *keyactions;
struct KeyAction *keyaction;
+ JsonBehavior *jsbehavior;
+ struct
+ {
+ JsonBehavior *on_empty;
+ JsonBehavior *on_error;
+ } on_behavior;
+ JsonQuotes js_quotes;
}
%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -639,7 +646,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_representation
json_value_expr
json_func_expr
+ json_value_func_expr
+ json_query_expr
+ json_exists_predicate
+ 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
@@ -651,15 +665,43 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
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
@@ -699,7 +741,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
@@ -710,8 +752,8 @@ 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
@@ -726,7 +768,8 @@ 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 JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG
+ JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG
+ JSON_QUERY JSON_VALUE
KEY KEYS KEEP
@@ -741,7 +784,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
@@ -749,7 +792,7 @@ 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
@@ -759,7 +802,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
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 STRIP_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
@@ -767,7 +810,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
@@ -846,7 +889,8 @@ 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 ABSENT UNIQUE JSON
+%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 '+' '-'
@@ -15336,6 +15380,80 @@ opt_asymmetric: ASYMMETRIC
/* SQL/JSON support */
json_func_expr:
json_value_constructor
+ | json_value_func_expr
+ | json_query_expr
+ | json_exists_predicate
+ ;
+
+
+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:
@@ -15374,6 +15492,153 @@ 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 must 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
{
@@ -15386,6 +15651,35 @@ json_output_clause_opt:
| /* 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
@@ -16056,6 +16350,7 @@ unreserved_keyword:
| COMMIT
| COMMITTED
| COMPRESSION
+ | CONDITIONAL
| CONFIGURATION
| CONFLICT
| CONNECTION
@@ -16092,10 +16387,12 @@ unreserved_keyword:
| DOUBLE_P
| DROP
| EACH
+ | EMPTY_P
| ENABLE_P
| ENCODING
| ENCRYPTED
| ENUM_P
+ | ERROR_P
| ESCAPE
| EVENT
| EXCLUDE
@@ -16145,6 +16442,7 @@ unreserved_keyword:
| INVOKER
| ISOLATION
| JSON
+ | KEEP
| KEY
| KEYS
| LABEL
@@ -16189,6 +16487,7 @@ unreserved_keyword:
| OFF
| OIDS
| OLD
+ | OMIT
| OPERATOR
| OPTION
| OPTIONS
@@ -16218,6 +16517,7 @@ unreserved_keyword:
| PROGRAM
| PUBLICATION
| QUOTE
+ | QUOTES
| RANGE
| READ
| REASSIGN
@@ -16299,6 +16599,7 @@ unreserved_keyword:
| UESCAPE
| UNBOUNDED
| UNCOMMITTED
+ | UNCONDITIONAL
| UNENCRYPTED
| UNKNOWN
| UNLISTEN
@@ -16358,8 +16659,11 @@ col_name_keyword:
| INTERVAL
| JSON_ARRAY
| JSON_ARRAYAGG
+ | JSON_EXISTS
| JSON_OBJECT
| JSON_OBJECTAGG
+ | JSON_QUERY
+ | JSON_VALUE
| LEAST
| NATIONAL
| NCHAR
@@ -16427,6 +16731,7 @@ type_func_name_keyword:
| OVERLAPS
| RIGHT
| SIMILAR
+ | STRING
| TABLESAMPLE
| VERBOSE
;
@@ -16591,6 +16896,7 @@ bare_label_keyword:
| COMMITTED
| COMPRESSION
| CONCURRENTLY
+ | CONDITIONAL
| CONFIGURATION
| CONFLICT
| CONNECTION
@@ -16643,11 +16949,13 @@ bare_label_keyword:
| DROP
| EACH
| ELSE
+ | EMPTY_P
| ENABLE_P
| ENCODING
| ENCRYPTED
| END_P
| ENUM_P
+ | ERROR_P
| ESCAPE
| EVENT
| EXCLUDE
@@ -16716,8 +17024,11 @@ bare_label_keyword:
| JSON
| JSON_ARRAY
| JSON_ARRAYAGG
+ | JSON_EXISTS
| JSON_OBJECT
| JSON_OBJECTAGG
+ | JSON_QUERY
+ | JSON_VALUE
| KEEP
| KEY
| KEYS
@@ -16777,6 +17088,7 @@ bare_label_keyword:
| OFF
| OIDS
| OLD
+ | OMIT
| ONLY
| OPERATOR
| OPTION
@@ -16813,6 +17125,7 @@ bare_label_keyword:
| PROGRAM
| PUBLICATION
| QUOTE
+ | QUOTES
| RANGE
| READ
| REAL
@@ -16881,6 +17194,7 @@ bare_label_keyword:
| STORAGE
| STORED
| STRICT_P
+ | STRING
| STRIP_P
| SUBSCRIPTION
| SUBSTRING
@@ -16914,6 +17228,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 6c793b72ec..2e549e7b39 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 0e504096fd..7709a6c665 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -86,6 +86,8 @@ static Node *transformJsonArrayQueryConstructor(ParseState *pstate,
static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg);
static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg);
static Node *transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *p);
+static Node *transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *p);
+static Node *transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve);
static Node *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -337,6 +339,14 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr);
break;
+ case T_JsonFuncExpr:
+ result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr);
+ break;
+
+ case T_JsonValueExpr:
+ result = transformJsonValueExpr(pstate, (JsonValueExpr *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3214,8 +3224,8 @@ makeCaseTestExpr(Node *expr)
* default format otherwise.
*/
static Node *
-transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
- JsonFormatType default_format)
+transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
+ JsonFormatType default_format, bool isarg)
{
Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr);
Node *rawexpr;
@@ -3234,6 +3244,8 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
get_type_category_preferred(exprtype, &typcategory, &typispreferred);
+ rawexpr = expr;
+
if (ve->format->format_type != JS_FORMAT_DEFAULT)
{
if (ve->format->encoding != JS_ENC_DEFAULT && exprtype != BYTEAOID)
@@ -3252,12 +3264,44 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
else
format = ve->format->format_type;
}
+ 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)
+ if (format == JS_FORMAT_DEFAULT)
+ expr = rawexpr;
+ else
{
Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
Node *orig = makeCaseTestExpr(expr);
@@ -3265,7 +3309,7 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
expr = orig;
- if (exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+ if (!isarg && exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg(ve->format->format_type == JS_FORMAT_DEFAULT ?
@@ -3316,6 +3360,24 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *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);
+}
+
+/*
+ * Transform JSON value expression using unspecified format by default.
+ */
+static Node *
+transformJsonValueExprDefault(ParseState *pstate, JsonValueExpr *jve)
+{
+ return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false);
+}
+
/*
* Checks specified output format for its applicability to the target type.
*/
@@ -3576,8 +3638,7 @@ transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor)
{
JsonKeyValue *kv = castNode(JsonKeyValue, lfirst(lc));
Node *key = transformExprRecurse(pstate, (Node *) kv->key);
- Node *val = transformJsonValueExpr(pstate, kv->value,
- JS_FORMAT_DEFAULT);
+ Node *val = transformJsonValueExprDefault(pstate, kv->value);
args = lappend(args, key);
args = lappend(args, val);
@@ -3755,7 +3816,7 @@ transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg)
Oid aggtype;
key = transformExprRecurse(pstate, (Node *) agg->arg->key);
- val = transformJsonValueExpr(pstate, agg->arg->value, JS_FORMAT_DEFAULT);
+ val = transformJsonValueExprDefault(pstate, agg->arg->value);
args = list_make2(key, val);
returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
@@ -3813,7 +3874,7 @@ transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg)
const char *aggfnname;
Oid aggtype;
- arg = transformJsonValueExpr(pstate, agg->arg, JS_FORMAT_DEFAULT);
+ arg = transformJsonValueExprDefault(pstate, agg->arg);
returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
list_make1(arg));
@@ -3861,8 +3922,7 @@ transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor)
foreach(lc, ctor->exprs)
{
JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc));
- Node *val = transformJsonValueExpr(pstate, jsval,
- JS_FORMAT_DEFAULT);
+ Node *val = transformJsonValueExprDefault(pstate, jsval);
args = lappend(args, val);
}
@@ -3945,3 +4005,413 @@ transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred)
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);
+
+ 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_type == JS_FORMAT_DEFAULT)
+ is_jsonb = exprType(context_item) == JSONBOID;
+ else
+ is_jsonb = ret->format->format_type == 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_type = 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_type = 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;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 204d285773..ef1eda6532 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1970,6 +1970,21 @@ FigureColnameInternal(Node *node, char **name)
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/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index d4c2e7b069..fb395b6f78 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -1017,11 +1017,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
* ----------
@@ -6666,3 +6661,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/jsonb.c b/src/backend/utils/adt/jsonb.c
index a103cbc7c6..d383cbdfed 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2227,3 +2227,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/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 03e7f10cd2..2023bcd125 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2777,11 +2777,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);
@@ -3253,6 +3253,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)
{
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 9be4e305ff..ca1cfe3d36 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->sval, 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 eff3734b6a..7811fa31e0 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 be8a75d8a7..fb52f77334 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -496,6 +496,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 ")
@@ -8099,6 +8101,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_WindowFunc:
case T_FuncExpr:
case T_JsonConstructorExpr:
+ case T_JsonExpr:
/* function-like: name(..) or name[..] */
return true;
@@ -8216,6 +8219,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;
@@ -8381,6 +8385,19 @@ get_rule_expr_paren(Node *node, deparse_context *context,
appendStringInfoChar(context->buf, ')');
}
+
+/*
+ * 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
*/
@@ -8424,6 +8441,66 @@ get_json_returning(JsonReturning *returning, StringInfo buf,
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
*
@@ -9583,6 +9660,7 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+
case T_JsonValueExpr:
{
JsonValueExpr *jve = (JsonValueExpr *) node;
@@ -9630,6 +9708,62 @@ get_rule_expr(Node *node, deparse_context *context,
}
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))->sval);
+ }
+ }
+
+ 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;
@@ -9753,6 +9887,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:
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index 8315812793..7120836c70 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -785,6 +785,27 @@ JumbleExpr(JumbleState *jstate, Node *node)
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)->sval);
+ }
+ 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/executor/execExpr.h b/src/include/executor/execExpr.h
index a41722ae1e..240d07982a 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 */
@@ -241,6 +242,7 @@ typedef enum ExprEvalOp
EEOP_SUBPLAN,
EEOP_JSON_CONSTRUCTOR,
EEOP_IS_JSON,
+ EEOP_JSONEXPR,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -687,6 +689,50 @@ typedef struct ExprEvalStep
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;
@@ -791,6 +837,14 @@ 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 344399f6a8..538d7eca07 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 380940968b..872f2f0828 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -109,6 +109,7 @@ extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_
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,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 666b45c5da..e5fa0fe4d9 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -205,6 +205,9 @@ typedef enum NodeTag
T_JsonReturning,
T_JsonValueExpr,
T_JsonConstructorExpr,
+ T_JsonExpr,
+ T_JsonCoercion,
+ T_JsonItemCoercions,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -501,8 +504,13 @@ typedef enum NodeTag
T_JsonAggConstructor,
T_JsonObjectAgg,
T_JsonArrayAgg,
+ T_JsonFuncExpr,
T_JsonIsPredicate,
+ T_JsonExistsPredicate,
+ T_JsonCommon,
+ T_JsonArgument,
T_JsonKeyValue,
+ T_JsonBehavior,
T_JsonOutput,
/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 40fb034028..157909b978 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1553,6 +1553,23 @@ typedef struct 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])
@@ -1564,6 +1581,48 @@ typedef struct JsonOutput
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
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3c4c248e29..97a00eb2db 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1235,6 +1235,17 @@ 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
@@ -1258,6 +1269,37 @@ typedef enum JsonFormatType
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
@@ -1345,6 +1387,73 @@ typedef struct JsonIsPredicate
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 1726d73da6..69590905c1 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -93,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)
@@ -147,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)
@@ -232,8 +235,12 @@ PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_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_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)
@@ -297,6 +304,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)
@@ -338,6 +346,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)
@@ -408,6 +417,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)
@@ -442,6 +452,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 851e787bfd..0a22af80a2 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/jsonb.h b/src/include/utils/jsonb.h
index 6bcf35dd0a..3fdff445cf 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -407,6 +407,9 @@ 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);
diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h
index cd16b6c0c8..62dc3d88a4 100644
--- a/src/include/utils/jsonfuncs.h
+++ b/src/include/utils/jsonfuncs.h
@@ -58,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 cd0b5d5b61..98a61d7f72 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/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_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
new file mode 100644
index 0000000000..1126d7caf5
--- /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 must 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 must 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 must 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 must 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/parallel_schedule b/src/test/regress/parallel_schedule
index 3ce701a588..b8cea3a5f2 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,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 sqljson
+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_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;
--
2.25.1
[text/x-patch] 0005-SQL-JSON-functions-for-json-type-v63.patch (57.0K, ../../[email protected]/6-0005-SQL-JSON-functions-for-json-type-v63.patch)
download | inline diff:
From 1c11ff98f52a0a84821c3ff66865357edc21d0cc Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:15:13 -0500
Subject: [PATCH 5/5] SQL JSON functions for json type
---
doc/src/sgml/func.sgml | 325 +++++++++++++++++-
doc/src/sgml/keywords/sql2016-02-reserved.txt | 3 +
src/backend/executor/execExpr.c | 45 +++
src/backend/executor/execExprInterp.c | 42 ++-
src/backend/nodes/copyfuncs.c | 35 ++
src/backend/nodes/equalfuncs.c | 25 ++
src/backend/parser/gram.y | 56 ++-
src/backend/parser/parse_expr.c | 152 +++++++-
src/backend/parser/parse_target.c | 9 +
src/backend/utils/adt/format_type.c | 4 +
src/backend/utils/adt/json.c | 51 ++-
src/backend/utils/adt/jsonb.c | 64 ++--
src/backend/utils/adt/ruleutils.c | 13 +-
src/include/executor/execExpr.h | 5 +
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 35 ++
src/include/nodes/primnodes.h | 5 +-
src/include/parser/kwlist.h | 4 +-
src/include/utils/json.h | 21 +-
src/include/utils/jsonb.h | 21 ++
src/test/regress/expected/sqljson.out | 267 ++++++++++++++
src/test/regress/sql/sqljson.sql | 57 +++
22 files changed, 1158 insertions(+), 84 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index decca95b1b..e19821c4b2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17621,11 +17621,21 @@ $.* ? (@ like_regex "^\\d+$")
<para>
<productname>PostgreSQL</productname> provides several functions
that generate JSON data. Taking values of SQL types as input, these
- functions construct JSON objects or JSON arrays represented as
- SQL character or binary strings.
+ 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>
@@ -17648,6 +17658,219 @@ $.* ? (@ like_regex "^\\d+$")
</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>
@@ -19080,6 +19303,104 @@ FROM
</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">
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 f6ec6fa846..25253f5377 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -47,6 +47,8 @@
#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"
@@ -2460,6 +2462,12 @@ ExecInitExprRec(Expr *node, ExprState *state,
{
ExecInitExprRec(ctor->func, state, resv, resnull);
}
+ else if ((ctor->type == JSCTOR_JSON_PARSE && !ctor->unique) ||
+ ctor->type == JSCTOR_JSON_SERIALIZE)
+ {
+ /* Use the value of the first argument as a result */
+ ExecInitExprRec(linitial(args), state, resv, resnull);
+ }
else
{
scratch.opcode = EEOP_JSON_CONSTRUCTOR;
@@ -2492,6 +2500,43 @@ ExecInitExprRec(Expr *node, ExprState *state,
argno++;
}
+ /* prepare type cache for datum_to_json[b]() */
+ if (ctor->type == JSCTOR_JSON_SCALAR)
+ {
+ bool is_jsonb =
+ ctor->returning->format->format_type == JS_FORMAT_JSONB;
+
+ 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);
}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index bd90b02be1..0483279f91 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3982,7 +3982,7 @@ ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op)
* JSON text validation.
*/
if (res && (pred->unique_keys || exprtype == TEXTOID))
- res = json_validate(json, pred->unique_keys);
+ res = json_validate(json, pred->unique_keys, false);
}
else if (exprtype == JSONBOID)
{
@@ -4533,6 +4533,46 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
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;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e5c9f403e6..c3242af8f2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2344,6 +2344,35 @@ _copyJsonValueExpr(const JsonValueExpr *from)
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
*/
@@ -5697,6 +5726,12 @@ copyObjectImpl(const void *from)
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;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a60e66970..731b1419f6 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -871,6 +871,25 @@ _equalJsonValueExpr(const JsonValueExpr *a, const JsonValueExpr *b)
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)
{
@@ -3584,6 +3603,12 @@ equal(const void *a, const void *b)
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;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff680b61d8..175e14e51d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -562,7 +562,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
@@ -649,6 +649,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
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
@@ -769,7 +772,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG
- JSON_QUERY JSON_VALUE
+ JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_VALUE
KEY KEYS KEEP
@@ -13132,6 +13135,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
@@ -13150,6 +13154,7 @@ ConstTypename:
| ConstBit { $$ = $1; }
| ConstCharacter { $$ = $1; }
| ConstDatetime { $$ = $1; }
+ | JsonType { $$ = $1; }
;
/*
@@ -13518,6 +13523,13 @@ interval_second:
}
;
+JsonType:
+ JSON
+ {
+ $$ = SystemTypeName("json");
+ $$->location = @1;
+ }
+ ;
/*****************************************************************************
*
@@ -15383,8 +15395,42 @@ json_func_expr:
| 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 ')'
+ {
+ JsonParseExpr *n = makeNode(JsonParseExpr);
+ n->expr = (JsonValueExpr *) $3;
+ n->unique_keys = $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_scalar_expr:
+ JSON_SCALAR '(' a_expr ')'
+ {
+ JsonScalarExpr *n = makeNode(JsonScalarExpr);
+ n->expr = (Expr *) $3;
+ 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 '('
@@ -16441,7 +16487,6 @@ unreserved_keyword:
| INSTEAD
| INVOKER
| ISOLATION
- | JSON
| KEEP
| KEY
| KEYS
@@ -16657,12 +16702,15 @@ 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
@@ -17028,6 +17076,8 @@ bare_label_keyword:
| JSON_OBJECT
| JSON_OBJECTAGG
| JSON_QUERY
+ | JSON_SCALAR
+ | JSON_SERIALIZE
| JSON_VALUE
| KEEP
| KEY
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 7709a6c665..bbd80e5c6e 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -88,6 +88,10 @@ 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,
@@ -347,6 +351,18 @@ transformExprRecurse(ParseState *pstate, Node *expr)
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));
@@ -3225,7 +3241,8 @@ makeCaseTestExpr(Node *expr)
*/
static Node *
transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
- JsonFormatType default_format, bool isarg)
+ JsonFormatType default_format, bool isarg,
+ Oid targettype)
{
Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr);
Node *rawexpr;
@@ -3299,17 +3316,17 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
else
format = default_format;
- if (format == JS_FORMAT_DEFAULT)
+ if (format == JS_FORMAT_DEFAULT &&
+ (!OidIsValid(targettype) || exprtype == targettype))
expr = rawexpr;
else
{
- Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
Node *orig = makeCaseTestExpr(expr);
Node *coerced;
+ bool cast_is_needed = OidIsValid(targettype);
- expr = orig;
-
- if (!isarg && exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+ if (!isarg && !cast_is_needed &&
+ exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg(ve->format->format_type == JS_FORMAT_DEFAULT ?
@@ -3318,6 +3335,8 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
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)
{
@@ -3325,6 +3344,9 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
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,
@@ -3335,11 +3357,21 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
if (!coerced)
{
/* If coercion failed, use to_json()/to_jsonb() functions. */
- Oid fnoid = targettype == JSONOID ? F_TO_JSON : F_TO_JSONB;
- FuncExpr *fexpr = makeFuncExpr(fnoid, targettype,
- list_make1(expr),
- InvalidOid, InvalidOid,
- COERCE_EXPLICIT_CALL);
+ FuncExpr *fexpr;
+ Oid fnoid;
+
+ 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;
@@ -3366,7 +3398,8 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
static Node *
transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve)
{
- return transformJsonValueExprExt(pstate, jve, JS_FORMAT_JSON, false);
+ return transformJsonValueExprExt(pstate, jve, JS_FORMAT_JSON, false,
+ InvalidOid);
}
/*
@@ -3375,7 +3408,8 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve)
static Node *
transformJsonValueExprDefault(ParseState *pstate, JsonValueExpr *jve)
{
- return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false);
+ return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false,
+ InvalidOid);
}
/*
@@ -4022,7 +4056,7 @@ transformJsonPassingArgs(ParseState *pstate, JsonFormatType format, List *args,
{
JsonArgument *arg = castNode(JsonArgument, lfirst(lc));
Node *expr = transformJsonValueExprExt(pstate, arg->val,
- format, true);
+ format, true, InvalidOid);
assign_expr_collations(pstate, expr);
@@ -4415,3 +4449,93 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
return (Node *) jsexpr;
}
+
+/*
+ * Transform a JSON() expression.
+ */
+static Node *
+transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr)
+{
+ JsonReturning *returning = makeNode(JsonReturning);
+ Node *arg;
+
+ returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1);
+ returning->typid = JSONOID;
+ returning->typmod = -1;
+
+ 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)
+{
+ JsonReturning *returning = makeNode(JsonReturning);
+ Node *arg = transformExprRecurse(pstate, (Node *) jsexpr->expr);
+
+ returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1);
+ returning->typid = JSONOID;
+ returning->typmod = -1;
+
+ 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 ef1eda6532..829c0f9497 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1957,6 +1957,15 @@ 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;
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 2918fdbfb6..060fd7e183 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -294,6 +294,10 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
else
buf = pstrdup("character varying");
break;
+
+ case JSONOID:
+ buf = pstrdup("json");
+ break;
}
if (buf == NULL)
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 5edcb8bb60..492796eb83 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -30,21 +30,6 @@
#include "utils/lsyscache.h"
#include "utils/typcache.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;
-
/* Common context for key uniqueness check */
typedef struct HTAB *JsonUniqueCheckState; /* hash table for key names */
@@ -99,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);
@@ -180,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)
@@ -762,6 +744,16 @@ 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)
{
@@ -802,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;
@@ -814,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));
}
/*
@@ -1712,7 +1699,7 @@ json_unique_object_field_start(void *_state, char *field, bool isnull)
/* Validate JSON text and additionally check key uniqueness */
bool
-json_validate(text *json, bool check_unique_keys)
+json_validate(text *json, bool check_unique_keys, bool throw_error)
{
JsonLexContext *lex = makeJsonLexContext(json, check_unique_keys);
JsonSemAction uniqueSemAction = {0};
@@ -1736,10 +1723,22 @@ json_validate(text *json, bool check_unique_keys)
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 */
}
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index d383cbdfed..2043f2e74a 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -34,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;
@@ -62,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);
@@ -71,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);
@@ -99,7 +77,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);
}
/*
@@ -123,7 +101,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);
}
/*
@@ -164,6 +142,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.
*/
@@ -254,7 +240,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;
@@ -264,6 +250,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;
@@ -298,6 +286,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
@@ -620,7 +609,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)
@@ -1127,6 +1116,18 @@ 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)
{
@@ -1168,7 +1169,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;
@@ -1180,11 +1180,7 @@ 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));
}
Datum
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fb52f77334..c4cac590af 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10052,7 +10052,9 @@ get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf)
if (ctor->unique)
appendStringInfoString(buf, " WITH UNIQUE KEYS");
- get_json_returning(ctor->returning, buf, true);
+ if (ctor->type != JSCTOR_JSON_PARSE &&
+ ctor->type != JSCTOR_JSON_SCALAR)
+ get_json_returning(ctor->returning, buf, true);
}
static void
@@ -10066,6 +10068,15 @@ get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context,
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;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 240d07982a..9ce8df17e5 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -680,6 +680,11 @@ typedef struct ExprEvalStep
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;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e5fa0fe4d9..90c1451e1c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -204,6 +204,9 @@ typedef enum NodeTag
T_JsonFormat,
T_JsonReturning,
T_JsonValueExpr,
+ T_JsonParseExpr,
+ T_JsonScalarExpr,
+ T_JsonSerializeExpr,
T_JsonConstructorExpr,
T_JsonExpr,
T_JsonCoercion,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 157909b978..13a18e02a2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1635,6 +1635,41 @@ typedef struct JsonKeyValue
JsonValueExpr *value; /* JSON value expression */
} JsonKeyValue;
+/*
+ * JsonParseExpr -
+ * untransformed representation of JSON()
+ */
+typedef struct JsonParseExpr
+{
+ NodeTag type;
+ JsonValueExpr *expr; /* string expression */
+ 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 */
+ 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
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 97a00eb2db..90108befeb 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1341,7 +1341,10 @@ typedef enum JsonConstructorType
JSCTOR_JSON_OBJECT = 1,
JSCTOR_JSON_ARRAY = 2,
JSCTOR_JSON_OBJECTAGG = 3,
- JSCTOR_JSON_ARRAYAGG = 4
+ JSCTOR_JSON_ARRAYAGG = 4,
+ JSCTOR_JSON_SCALAR = 5,
+ JSCTOR_JSON_SERIALIZE = 6,
+ JSCTOR_JSON_PARSE = 7
} JsonConstructorType;
/*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 69590905c1..ab3d8e0b52 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -232,13 +232,15 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL)
PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
-PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json", JSON, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_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)
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index bfe5b21591..da4a9257b3 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -16,16 +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);
+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 3fdff445cf..bae466b523 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -376,6 +376,22 @@ 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;
/* Support functions */
extern uint32 getJsonbOffset(const JsonbContainer *jc, int index);
@@ -403,6 +419,7 @@ 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,
@@ -418,6 +435,10 @@ extern Datum jsonb_set_element(Jsonb *jb, Datum *path, int path_len,
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);
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 27dca7815a..11f5eb2d2c 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -1,3 +1,270 @@
+-- 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)
+
+-- JSON_SCALAR()
+SELECT JSON_SCALAR();
+ERROR: syntax error at or near ")"
+LINE 1: SELECT JSON_SCALAR();
+ ^
+SELECT JSON_SCALAR(NULL);
+ json_scalar
+-------------
+
+(1 row)
+
+SELECT JSON_SCALAR(NULL::int);
+ json_scalar
+-------------
+
+(1 row)
+
+SELECT JSON_SCALAR(123);
+ json_scalar
+-------------
+ 123
+(1 row)
+
+SELECT JSON_SCALAR(123.45);
+ json_scalar
+-------------
+ 123.45
+(1 row)
+
+SELECT JSON_SCALAR(123.45::numeric);
+ json_scalar
+-------------
+ 123.45
+(1 row)
+
+SELECT JSON_SCALAR(true);
+ json_scalar
+-------------
+ true
+(1 row)
+
+SELECT JSON_SCALAR(false);
+ json_scalar
+-------------
+ false
+(1 row)
+
+SELECT JSON_SCALAR(' 123.45');
+ json_scalar
+-------------
+ " 123.45"
+(1 row)
+
+SELECT JSON_SCALAR('2020-06-07'::date);
+ json_scalar
+--------------
+ "2020-06-07"
+(1 row)
+
+SELECT JSON_SCALAR('2020-06-07 01:02:03'::timestamp);
+ json_scalar
+-----------------------
+ "2020-06-07T01:02:03"
+(1 row)
+
+SELECT JSON_SCALAR('{}'::json);
+ json_scalar
+-------------
+ {}
+(1 row)
+
+SELECT JSON_SCALAR('{}'::jsonb);
+ json_scalar
+-------------
+ {}
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123);
+ QUERY PLAN
+----------------------------
+ Result
+ Output: JSON_SCALAR(123)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123');
+ QUERY PLAN
+------------------------------------
+ Result
+ Output: JSON_SCALAR('123'::text)
+(2 rows)
+
+-- JSON_SERIALIZE()
+SELECT JSON_SERIALIZE();
+ERROR: syntax error at or near ")"
+LINE 1: SELECT JSON_SERIALIZE();
+ ^
+SELECT JSON_SERIALIZE(NULL);
+ json_serialize
+----------------
+
+(1 row)
+
+SELECT JSON_SERIALIZE(JSON('{ "a" : 1 } '));
+ json_serialize
+----------------
+ { "a" : 1 }
+(1 row)
+
+SELECT JSON_SERIALIZE('{ "a" : 1 } ');
+ json_serialize
+----------------
+ { "a" : 1 }
+(1 row)
+
+SELECT JSON_SERIALIZE('1');
+ json_serialize
+----------------
+ 1
+(1 row)
+
+SELECT JSON_SERIALIZE('1' FORMAT JSON);
+ json_serialize
+----------------
+ 1
+(1 row)
+
+SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING bytea);
+ json_serialize
+----------------------------
+ \x7b20226122203a2031207d20
+(1 row)
+
+SELECT 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
diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
index 4f3c06dcb3..98bd93c110 100644
--- a/src/test/regress/sql/sqljson.sql
+++ b/src/test/regress/sql/sqljson.sql
@@ -1,3 +1,60 @@
+-- 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);
+
+
+-- JSON_SCALAR()
+SELECT JSON_SCALAR();
+SELECT JSON_SCALAR(NULL);
+SELECT JSON_SCALAR(NULL::int);
+SELECT JSON_SCALAR(123);
+SELECT JSON_SCALAR(123.45);
+SELECT JSON_SCALAR(123.45::numeric);
+SELECT JSON_SCALAR(true);
+SELECT JSON_SCALAR(false);
+SELECT JSON_SCALAR(' 123.45');
+SELECT JSON_SCALAR('2020-06-07'::date);
+SELECT JSON_SCALAR('2020-06-07 01:02:03'::timestamp);
+SELECT JSON_SCALAR('{}'::json);
+SELECT JSON_SCALAR('{}'::jsonb);
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123');
+
+-- JSON_SERIALIZE()
+SELECT JSON_SERIALIZE();
+SELECT JSON_SERIALIZE(NULL);
+SELECT JSON_SERIALIZE(JSON('{ "a" : 1 } '));
+SELECT JSON_SERIALIZE('{ "a" : 1 } ');
+SELECT JSON_SERIALIZE('1');
+SELECT JSON_SERIALIZE('1' FORMAT JSON);
+SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING bytea);
+SELECT 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);
--
2.25.1
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-05 14:39 Andrew Dunstan <[email protected]>
parent: Andrew Dunstan <[email protected]>
0 siblings, 2 replies; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-05 14:39 UTC (permalink / raw)
To: Himanshu Upadhyaya <[email protected]>; +Cc: Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 3/4/22 11:28, Andrew Dunstan wrote:
> On 3/2/22 10:19, Andrew Dunstan wrote:
>> On 3/1/22 16:41, Andrew Dunstan wrote:
>>> On 2/1/22 14:11,I wrote:
>>>> 2. The new GUC "sql_json" is a bit of a worry. I understand what it's
>>>> trying to do, but I'm trying to convince myself it's not going to be a
>>>> fruitful source of error reports, especially if people switch it in the
>>>> middle of a session. Maybe it should be an initdb option instead of a GUC?
>>>>
>>>>
>>> So far my efforts have not borne fruit. Here's why:
>>>
>>>
>>> andrew=# set sql_json = jsonb;
>>> SET
>>> andrew=# create table abc (x text, y json);
>>> CREATE TABLE
>>> andrew=# \d abc
>>> Table "public.abc"
>>> Column | Type | Collation | Nullable | Default
>>> --------+------+-----------+----------+---------
>>> x | text | | |
>>> y | json | | |
>>>
>>> andrew=# insert into abc values ('a','{"q":1}');
>>> INSERT 0 1
>>> andrew=# select json_each(y) from abc;
>>> ERROR: function json_each(json) does not exist
>>> LINE 1: select json_each(y) from abc;
>>> ^
>>> HINT: No function matches the given name and argument types. You might
>>> need to add explicit type casts.
>>> andrew=# select jsonb_each(y) from abc;
>>> jsonb_each
>>> ------------
>>> (q,1)
>>> (1 row)
>>>
>>>
>>> The description tells them the column is json, but the json_* functions
>>> don't work on the column and you need to use the jsonb functions. That
>>> seems to me a recipe for major confusion. It might be better if we set
>>> it at initdb time so it couldn't be changed, but even so it could be
>>> horribly confusing.
>>>
>>> This is certainly severable from the rest of these patches. I'm not sure
>>> how severable it is from the SQL/JSON Table patches.
>>>
>>>
>> I have confirmed that this is not required at all for the JSON_TABLE
>> patch set.
>>
>>
>> I'll submit new patch sets omitting it shortly. The GUC patch can be
>> considered separately, probably as release 16 material, but I think as
>> is it's at best quite incomplete.
>
> here's a new set of patches, omitting the GUC patch and with the
> beginnings of some message cleanup - there's more work to do there.
>
>
This patchset restores the RETURNING clause for JSON() and JSON_SCALAR()
but without the GUC
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
Attachments:
[text/x-patch] 0001-Common-SQL-JSON-clauses-v64.patch (30.8K, ../../[email protected]/2-0001-Common-SQL-JSON-clauses-v64.patch)
download | inline diff:
From 1a7402252e76db3e8f839db5c1020dff05c55b89 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:00:49 -0500
Subject: [PATCH 1/6] Common SQL/JSON clauses
---
src/backend/executor/execExpr.c | 22 ++++
src/backend/nodes/copyfuncs.c | 55 ++++++++
src/backend/nodes/equalfuncs.c | 39 ++++++
src/backend/nodes/makefuncs.c | 54 ++++++++
src/backend/nodes/nodeFuncs.c | 66 ++++++++++
src/backend/nodes/outfuncs.c | 39 ++++++
src/backend/nodes/readfuncs.c | 51 ++++++++
src/backend/optimizer/util/clauses.c | 23 ++++
src/backend/parser/gram.y | 65 +++++++++-
src/backend/parser/parse_expr.c | 181 +++++++++++++++++++++++++++
src/backend/utils/adt/ruleutils.c | 56 +++++++++
src/backend/utils/misc/queryjumble.c | 26 ++++
src/include/nodes/makefuncs.h | 5 +
src/include/nodes/nodes.h | 4 +
src/include/nodes/parsenodes.h | 13 ++
src/include/nodes/primnodes.h | 59 +++++++++
src/include/parser/kwlist.h | 2 +
17 files changed, 758 insertions(+), 2 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 847357bf80..939e92457d 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2428,6 +2428,28 @@ 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;
+ }
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d4f8455a2b..55c36b46a8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2298,6 +2298,52 @@ _copyOnConflictExpr(const OnConflictExpr *from)
return newnode;
}
+
+/*
+ * _copyJsonFormat
+ */
+static JsonFormat *
+_copyJsonFormat(const JsonFormat *from)
+{
+ JsonFormat *newnode = makeNode(JsonFormat);
+
+ COPY_SCALAR_FIELD(format_type);
+ 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;
+}
+
/* ****************************************************************
* pathnodes.h copy functions
*
@@ -5350,6 +5396,15 @@ 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;
/*
* RELATION NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f1002afe7a..0ddebd066e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -841,6 +841,36 @@ _equalOnConflictExpr(const OnConflictExpr *a, const OnConflictExpr *b)
return true;
}
+static bool
+_equalJsonFormat(const JsonFormat *a, const JsonFormat *b)
+{
+ COMPARE_SCALAR_FIELD(format_type);
+ 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;
+}
+
/*
* Stuff from pathnodes.h
*/
@@ -3358,6 +3388,15 @@ 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;
/*
* RELATION NODES
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index c85d8fe975..867a927e7a 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"
@@ -818,3 +819,56 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+/*
+ * makeJsonFormat -
+ * creates a JsonFormat node
+ */
+JsonFormat *
+makeJsonFormat(JsonFormatType type, JsonEncoding encoding, int location)
+{
+ JsonFormat *jf = makeNode(JsonFormat);
+
+ jf->format_type = type;
+ jf->encoding = encoding;
+ jf->location = location;
+
+ return jf;
+}
+
+/*
+ * makeJsonValueExpr -
+ * creates a JsonValueExpr node
+ */
+JsonValueExpr *
+makeJsonValueExpr(Expr *expr, JsonFormat *format)
+{
+ JsonValueExpr *jve = makeNode(JsonValueExpr);
+
+ jve->raw_expr = expr;
+ jve->formatted_expr = NULL;
+ jve->format = format;
+
+ return jve;
+}
+
+/*
+ * makeJsonEncoding -
+ * converts JSON encoding name to enum JsonEncoding
+ */
+JsonEncoding
+makeJsonEncoding(char *name)
+{
+ if (!pg_strcasecmp(name, "utf8"))
+ return JS_ENC_UTF8;
+ if (!pg_strcasecmp(name, "utf16"))
+ return JS_ENC_UTF16;
+ if (!pg_strcasecmp(name, "utf32"))
+ return JS_ENC_UTF32;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized JSON encoding: %s", name)));
+
+ return JS_ENC_DEFAULT;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 47d0564fa2..f081393575 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -250,6 +250,13 @@ 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;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -482,6 +489,8 @@ 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);
default:
break;
}
@@ -956,6 +965,9 @@ 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;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
@@ -1168,6 +1180,10 @@ 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;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1614,6 +1630,9 @@ 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;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2348,6 +2367,16 @@ 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;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -2678,6 +2707,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:
{
@@ -3309,6 +3339,28 @@ 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;
+ }
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -4017,6 +4069,20 @@ 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;
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 6bdad462c7..449d90c8f4 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1751,6 +1751,36 @@ _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_type, 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);
+}
+
/*****************************************************************************
*
* Stuff from pathnodes.h.
@@ -4537,6 +4567,15 @@ 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;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3f68f7c18d..6f398cdc15 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1389,6 +1389,51 @@ _readOnConflictExpr(void)
READ_DONE();
}
+/*
+ * _readJsonFormat
+ */
+static JsonFormat *
+_readJsonFormat(void)
+{
+ READ_LOCALS(JsonFormat);
+
+ READ_ENUM_FIELD(format_type, 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();
+}
+
/*
* Stuff from pathnodes.h.
*
@@ -2974,6 +3019,12 @@ 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
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a707dc9f26..5e2754e789 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -3512,6 +3512,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 a03b33b53b..0192321d9a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -635,6 +635,14 @@ 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_output_clause_opt
+
+%type <ival> json_encoding
+ json_encoding_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
@@ -686,7 +694,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXTENSION EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
- FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
+ FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
@@ -697,7 +705,7 @@ 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
KEY
@@ -781,6 +789,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
@@ -15226,6 +15235,54 @@ opt_asymmetric: ASYMMETRIC
| /*EMPTY*/
;
+/* SQL/JSON support */
+
+json_value_expr:
+ a_expr json_format_clause_opt
+ {
+ $$ = (Node *) makeJsonValueExpr((Expr *) $1, $2);
+ }
+ ;
+
+json_format_clause_opt:
+ FORMAT json_representation
+ {
+ $$ = $2;
+ $$.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_output_clause_opt:
+ RETURNING Typename json_format_clause_opt
+ {
+ JsonOutput *n = makeNode(JsonOutput);
+ n->typeName = $2;
+ n->returning.format = $3;
+ $$ = (Node *) n;
+ }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
/*****************************************************************************
*
@@ -15767,6 +15824,7 @@ unreserved_keyword:
| FIRST_P
| FOLLOWING
| FORCE
+ | FORMAT
| FORWARD
| FUNCTION
| FUNCTIONS
@@ -15798,6 +15856,7 @@ unreserved_keyword:
| INSTEAD
| INVOKER
| ISOLATION
+ | JSON
| KEY
| LABEL
| LANGUAGE
@@ -16314,6 +16373,7 @@ bare_label_keyword:
| FOLLOWING
| FORCE
| FOREIGN
+ | FORMAT
| FORWARD
| FREEZE
| FULL
@@ -16358,6 +16418,7 @@ bare_label_keyword:
| IS
| ISOLATION
| JOIN
+ | JSON
| KEY
| LABEL
| LANGUAGE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 1c09ea24cd..985ddbedf1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -34,6 +34,7 @@
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/date.h"
+#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/timestamp.h"
#include "utils/xml.h"
@@ -3099,3 +3100,183 @@ ParseExprKindName(ParseExprKind exprKind)
}
return "unrecognized expression kind";
}
+
+/*
+ * Make string Const node from JSON encoding name.
+ *
+ * UTF8 is default encoding.
+ */
+static Const *
+getJsonEncodingConst(JsonFormat *format)
+{
+ JsonEncoding encoding;
+ const char *enc;
+ Name encname = palloc(sizeof(NameData));
+
+ if (!format ||
+ format->format_type == JS_FORMAT_DEFAULT ||
+ format->encoding == JS_ENC_DEFAULT)
+ encoding = JS_ENC_UTF8;
+ else
+ encoding = format->encoding;
+
+ switch (encoding)
+ {
+ case JS_ENC_UTF16:
+ enc = "UTF16";
+ break;
+ case JS_ENC_UTF32:
+ enc = "UTF32";
+ break;
+ case JS_ENC_UTF8:
+ enc = "UTF8";
+ break;
+ default:
+ elog(ERROR, "invalid JSON encoding: %d", encoding);
+ break;
+ }
+
+ namestrcpy(encname, enc);
+
+ return makeConst(NAMEOID, -1, InvalidOid, NAMEDATALEN,
+ NameGetDatum(encname), false, false);
+}
+
+/*
+ * Make bytea => text conversion using specified JSON format encoding.
+ */
+static Node *
+makeJsonByteaToTextConversion(Node *expr, JsonFormat *format, int location)
+{
+ Const *encoding = getJsonEncodingConst(format);
+ FuncExpr *fexpr = makeFuncExpr(F_CONVERT_FROM, TEXTOID,
+ list_make2(expr, encoding),
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+
+ fexpr->location = location;
+
+ return (Node *) fexpr;
+}
+
+/*
+ * Make CaseTestExpr node.
+ */
+static Node *
+makeCaseTestExpr(Node *expr)
+{
+ CaseTestExpr *placeholder = makeNode(CaseTestExpr);
+
+ placeholder->typeId = exprType(expr);
+ placeholder->typeMod = exprTypmod(expr);
+ placeholder->collation = exprCollation(expr);
+
+ return (Node *) placeholder;
+}
+
+/*
+ * Transform JSON value expression using specified input JSON format or
+ * default format otherwise.
+ */
+static Node *
+transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
+ JsonFormatType default_format)
+{
+ Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr);
+ Node *rawexpr;
+ JsonFormatType format;
+ Oid exprtype;
+ int location;
+ char typcategory;
+ bool typispreferred;
+
+ 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);
+
+ if (ve->format->format_type != JS_FORMAT_DEFAULT)
+ {
+ if (ve->format->encoding != JS_ENC_DEFAULT && exprtype != BYTEAOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("JSON ENCODING clause is only allowed for bytea input type"),
+ parser_errposition(pstate, ve->format->location)));
+
+ if (exprtype == JSONOID || exprtype == JSONBOID)
+ {
+ format = JS_FORMAT_DEFAULT; /* do not format json[b] types */
+ ereport(WARNING,
+ (errmsg("FORMAT JSON has no effect for json and jsonb types"),
+ parser_errposition(pstate, ve->format->location)));
+ }
+ else
+ format = ve->format->format_type;
+ }
+ else if (exprtype == JSONOID || exprtype == JSONBOID)
+ format = JS_FORMAT_DEFAULT; /* do not format json[b] types */
+ else
+ format = default_format;
+
+ if (format != JS_FORMAT_DEFAULT)
+ {
+ Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
+ Node *orig = makeCaseTestExpr(expr);
+ Node *coerced;
+
+ expr = orig;
+
+ if (exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg(ve->format->format_type == JS_FORMAT_DEFAULT ?
+ "cannot use non-string types with implicit FORMAT JSON clause" :
+ "cannot use non-string types with explicit FORMAT JSON clause"),
+ parser_errposition(pstate, ve->format->location >= 0 ?
+ ve->format->location : location)));
+
+ /* Convert encoded JSON text from bytea. */
+ if (format == JS_FORMAT_JSON && exprtype == BYTEAOID)
+ {
+ expr = makeJsonByteaToTextConversion(expr, ve->format, location);
+ exprtype = TEXTOID;
+ }
+
+ /* Try to coerce to the target type. */
+ coerced = coerce_to_target_type(pstate, expr, exprtype,
+ targettype, -1,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+
+ if (!coerced)
+ {
+ /* If coercion failed, use to_json()/to_jsonb() functions. */
+ Oid fnoid = targettype == JSONOID ? F_TO_JSON : F_TO_JSONB;
+ FuncExpr *fexpr = makeFuncExpr(fnoid, targettype,
+ list_make1(expr),
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+ fexpr->location = location;
+
+ coerced = (Node *) fexpr;
+ }
+
+ if (coerced == orig)
+ expr = rawexpr;
+ else
+ {
+ ve = copyObject(ve);
+ ve->raw_expr = (Expr *) rawexpr;
+ ve->formatted_expr = (Expr *) coerced;
+
+ expr = (Node *) ve;
+ }
+ }
+
+ return expr;
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b16526e65e..f34f5199fa 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8262,6 +8262,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;
}
@@ -8367,6 +8372,48 @@ get_rule_expr_paren(Node *node, deparse_context *context,
appendStringInfoChar(context->buf, ')');
}
+/*
+ * get_json_format - Parse back a JsonFormat node
+ */
+static void
+get_json_format(JsonFormat *format, deparse_context *context)
+{
+ if (format->format_type == JS_FORMAT_DEFAULT)
+ return;
+
+ appendStringInfoString(context->buf,
+ format->format_type == 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(context->buf, " ENCODING %s", encoding);
+ }
+}
+
+/*
+ * get_json_returning - Parse back a JsonReturning structure
+ */
+static void
+get_json_returning(JsonReturning *returning, deparse_context *context,
+ bool json_format_by_default)
+{
+ if (!OidIsValid(returning->typid))
+ return;
+
+ appendStringInfo(context->buf, " RETURNING %s",
+ format_type_with_typemod(returning->typid,
+ returning->typmod));
+
+ if (!json_format_by_default ||
+ returning->format->format_type !=
+ (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON))
+ get_json_format(returning->format, context);
+}
/* ----------
* get_rule_expr - Parse back an expression
@@ -9527,6 +9574,15 @@ 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);
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index a67487e5fe..84435420e4 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -737,6 +737,32 @@ 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_List:
foreach(temp, (List *) node)
{
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 50de4c62af..ec8b71a685 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -106,4 +106,9 @@ 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 JsonEncoding makeJsonEncoding(char *name);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 5d075f0c34..59737f1034 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -201,6 +201,9 @@ typedef enum NodeTag
T_FromExpr,
T_OnConflictExpr,
T_IntoClause,
+ T_JsonFormat,
+ T_JsonReturning,
+ T_JsonValueExpr,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -491,6 +494,7 @@ typedef enum NodeTag
T_VacuumRelation,
T_PublicationObjSpec,
T_PublicationTable,
+ 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 1617702d9d..b9a8f9af76 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1551,6 +1551,19 @@ typedef struct TriggerTransition
bool isTable;
} TriggerTransition;
+/* Nodes for SQL/JSON support */
+
+/*
+ * JsonOutput -
+ * representation of JSON output clause (RETURNING type [FORMAT format])
+ */
+typedef struct JsonOutput
+{
+ NodeTag type;
+ TypeName *typeName; /* RETURNING type name, if specified */
+ JsonReturning returning; /* RETURNING FORMAT clause and type Oids */
+} JsonOutput;
+
/*****************************************************************************
* Raw Grammar Output Statements
*****************************************************************************/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index dab5c4ff5d..118a0d6b49 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1235,6 +1235,65 @@ typedef struct XmlExpr
int location; /* token location, or -1 if unknown */
} XmlExpr;
+/*
+ * JsonEncoding -
+ * representation of JSON ENCODING clause
+ */
+typedef enum JsonEncoding
+{
+ JS_ENC_DEFAULT, /* unspecified */
+ JS_ENC_UTF8,
+ JS_ENC_UTF16,
+ JS_ENC_UTF32,
+} JsonEncoding;
+
+/*
+ * JsonFormatType -
+ * enumeration of JSON formats used in JSON FORMAT clause
+ */
+typedef enum JsonFormatType
+{
+ JS_FORMAT_DEFAULT, /* unspecified */
+ JS_FORMAT_JSON, /* FORMAT JSON [ENCODING ...] */
+ JS_FORMAT_JSONB /* implicit internal format for RETURNING jsonb */
+} JsonFormatType;
+
+/*
+ * JsonFormat -
+ * representation of JSON FORMAT clause
+ */
+typedef struct JsonFormat
+{
+ NodeTag type;
+ JsonFormatType format_type; /* format type */
+ JsonEncoding encoding; /* JSON encoding */
+ int location; /* token location, or -1 if unknown */
+} JsonFormat;
+
+/*
+ * JsonReturning -
+ * transformed representation of JSON RETURNING clause
+ */
+typedef struct JsonReturning
+{
+ NodeTag type;
+ JsonFormat *format; /* output JSON format */
+ Oid typid; /* target type Oid */
+ int32 typmod; /* target type modifier */
+} JsonReturning;
+
+/*
+ * JsonValueExpr -
+ * representation of JSON value expression (expr [FORMAT json_format])
+ */
+typedef struct JsonValueExpr
+{
+ NodeTag type;
+ Expr *raw_expr; /* raw expression */
+ Expr *formatted_expr; /* formatted expression or NULL */
+ JsonFormat *format; /* FORMAT clause, if specified */
+} JsonValueExpr;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index bcef7eed2f..f3502b8be4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -175,6 +175,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,6 +228,7 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL)
PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD, BARE_LABEL)
--
2.25.1
[text/x-patch] 0002-SQL-JSON-constructors-v64.patch (186.8K, ../../[email protected]/3-0002-SQL-JSON-constructors-v64.patch)
download | inline diff:
From f7f9f19fd8d8f77fa9788ae253d9047ac326ae37 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:02:10 -0500
Subject: [PATCH 2/6] SQL/JSON constructors
---
doc/src/sgml/func.sgml | 947 +++++++++++++++++++++++
src/backend/executor/execExpr.c | 63 ++
src/backend/executor/execExprInterp.c | 48 ++
src/backend/jit/llvm/llvmjit_expr.c | 6 +
src/backend/jit/llvm/llvmjit_types.c | 1 +
src/backend/nodes/copyfuncs.c | 173 +++++
src/backend/nodes/equalfuncs.c | 132 ++++
src/backend/nodes/makefuncs.c | 15 +
src/backend/nodes/nodeFuncs.c | 151 ++++
src/backend/nodes/outfuncs.c | 18 +
src/backend/nodes/readfuncs.c | 22 +
src/backend/optimizer/util/clauses.c | 23 +
src/backend/parser/gram.y | 265 ++++++-
src/backend/parser/parse_expr.c | 589 ++++++++++++++
src/backend/parser/parse_target.c | 13 +
src/backend/parser/parser.c | 16 +
src/backend/utils/adt/json.c | 422 +++++++++-
src/backend/utils/adt/jsonb.c | 224 +++++-
src/backend/utils/adt/jsonb_util.c | 24 +-
src/backend/utils/adt/ruleutils.c | 212 ++++-
src/backend/utils/misc/queryjumble.c | 12 +
src/include/catalog/pg_aggregate.dat | 22 +
src/include/catalog/pg_proc.dat | 70 ++
src/include/executor/execExpr.h | 14 +
src/include/nodes/makefuncs.h | 1 +
src/include/nodes/nodes.h | 8 +
src/include/nodes/parsenodes.h | 96 ++-
src/include/nodes/primnodes.h | 25 +
src/include/parser/kwlist.h | 6 +
src/include/utils/json.h | 6 +
src/include/utils/jsonb.h | 9 +
src/interfaces/ecpg/preproc/parse.pl | 2 +
src/interfaces/ecpg/preproc/parser.c | 14 +
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/expected/sqljson.out | 746 ++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/opr_sanity.sql | 6 +-
src/test/regress/sql/sqljson.sql | 282 +++++++
38 files changed, 4561 insertions(+), 130 deletions(-)
create mode 100644 src/test/regress/expected/sqljson.out
create mode 100644 src/test/regress/sql/sqljson.sql
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df3cd5987b..faf0c92f76 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17564,6 +17564,844 @@ $.* ? (@ 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>
+
+ <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 or JSON arrays represented as
+ SQL character or binary strings.
+ </para>
+
+ <itemizedlist>
+ <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-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="sqljson-common-clauses">
+ <title>SQL/JSON Common Clauses</title>
+
+ <sect4 id="sqljson-output-clause">
+ <title>SQL/JSON Output Clause</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable> [ FORMAT JSON [ ENCODING UTF8 ] ]</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">
@@ -19719,6 +20557,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/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 939e92457d..31e628b098 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2450,6 +2450,69 @@ ExecInitExprRec(Expr *node, ExprState *state,
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
+ {
+ 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++;
+ }
+
+ 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;
+
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 d6f7d7c2d7..880cdc8e01 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -71,6 +71,8 @@
#include "utils/date.h"
#include "utils/datum.h"
#include "utils/expandedrecord.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -477,6 +479,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_GROUPING_FUNC,
&&CASE_EEOP_WINDOW_FUNC,
&&CASE_EEOP_SUBPLAN,
+ &&CASE_EEOP_JSON_CONSTRUCTOR,
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
&&CASE_EEOP_AGG_DESERIALIZE,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1786,7 +1789,13 @@ 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();
}
@@ -4386,3 +4395,42 @@ 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_type == 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
+ {
+ res = (Datum) 0;
+ elog(ERROR, "invalid JsonConstructorExpr type %d", ctor->type);
+ }
+
+ *op->resvalue = res;
+ *op->resnull = isnull;
+}
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index bd86f546d7..d0c26cf58b 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2348,6 +2348,12 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_JSON_CONSTRUCTOR:
+ build_EvalXFunc(b, mod, "ExecEvalJsonConstructor",
+ v_state, op, v_econtext);
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ break;
+
case EEOP_LAST:
Assert(false);
break;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index d5191cf02b..53c75dd9d6 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -131,6 +131,7 @@ void *referenced_functions[] =
ExecEvalSysVar,
ExecEvalWholeRowVar,
ExecEvalXmlExpr,
+ ExecEvalJsonConstructor,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 55c36b46a8..d89596a74a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2344,6 +2344,152 @@ _copyJsonValueExpr(const JsonValueExpr *from)
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;
+}
+
/* ****************************************************************
* pathnodes.h copy functions
*
@@ -5405,6 +5551,33 @@ copyObjectImpl(const void *from)
case T_JsonValueExpr:
retval = _copyJsonValueExpr(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;
/*
* RELATION NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0ddebd066e..9cb8495ddf 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -871,6 +871,111 @@ _equalJsonValueExpr(const JsonValueExpr *a, const JsonValueExpr *b)
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;
+}
+
/*
* Stuff from pathnodes.h
*/
@@ -3397,6 +3502,9 @@ equal(const void *a, const void *b)
case T_JsonValueExpr:
retval = _equalJsonValueExpr(a, b);
break;
+ case T_JsonConstructorExpr:
+ retval = _equalJsonConstructorExpr(a, b);
+ break;
/*
* RELATION NODES
@@ -3977,6 +4085,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 867a927e7a..7b4f7972e6 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -872,3 +872,18 @@ makeJsonEncoding(char *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;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index f081393575..120803a841 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -257,6 +257,9 @@ exprType(const Node *expr)
type = exprType((Node *) (jve->formatted_expr ? jve->formatted_expr : jve->raw_expr));
}
break;
+ case T_JsonConstructorExpr:
+ type = ((const JsonConstructorExpr *) expr)->returning->typid;
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -491,6 +494,8 @@ exprTypmod(const Node *expr)
return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
case T_JsonValueExpr:
return exprTypmod((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
+ case T_JsonConstructorExpr:
+ return -1; /* ((const JsonConstructorExpr *) expr)->returning->typmod; */
default:
break;
}
@@ -968,6 +973,16 @@ exprCollation(const Node *expr)
case T_JsonValueExpr:
coll = exprCollation((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
break;
+ case T_JsonConstructorExpr:
+ {
+ const JsonConstructorExpr *ctor = (const JsonConstructorExpr *) expr;
+
+ if (ctor->coercion)
+ coll = exprCollation((Node *) ctor->coercion);
+ else
+ coll = InvalidOid;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
@@ -1184,6 +1199,16 @@ exprSetCollation(Node *expr, Oid collation)
exprSetCollation((Node *) ((JsonValueExpr *) expr)->formatted_expr,
collation);
break;
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *ctor = (JsonConstructorExpr *) expr;
+
+ if (ctor->coercion)
+ exprSetCollation((Node *) ctor->coercion, collation);
+ else
+ Assert(!OidIsValid(collation)); /* result is always a json[b] type */
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1633,6 +1658,9 @@ exprLocation(const Node *expr)
case T_JsonValueExpr:
loc = exprLocation((Node *) ((const JsonValueExpr *) expr)->raw_expr);
break;
+ case T_JsonConstructorExpr:
+ loc = ((const JsonConstructorExpr *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2377,6 +2405,18 @@ expression_tree_walker(Node *node,
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;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -3359,6 +3399,19 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->formatted_expr, jve->formatted_expr, Expr *);
MUTATE(newnode->format, jve->format, JsonFormat *);
+ return (Node *) newnode;
+ }
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *jve = (JsonConstructorExpr *) node;
+ JsonConstructorExpr *newnode;
+
+ FLATCOPY(newnode, jve, JsonConstructorExpr);
+ MUTATE(newnode->args, jve->args, List *);
+ MUTATE(newnode->func, jve->func, Expr *);
+ MUTATE(newnode->coercion, jve->coercion, Expr *);
+ MUTATE(newnode->returning, jve->returning, JsonReturning *);
+
return (Node *) newnode;
}
default:
@@ -4083,6 +4136,104 @@ raw_expression_tree_walker(Node *node,
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;
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 449d90c8f4..c25f0bd684 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1781,6 +1781,21 @@ _outJsonValueExpr(StringInfo str, const JsonValueExpr *node)
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);
+}
+
/*****************************************************************************
*
* Stuff from pathnodes.h.
@@ -4576,6 +4591,9 @@ outNode(StringInfo str, const void *obj)
case T_JsonValueExpr:
_outJsonValueExpr(str, obj);
break;
+ case T_JsonConstructorExpr:
+ _outJsonConstructorExpr(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6f398cdc15..e0b3ad1ed2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1434,6 +1434,26 @@ _readJsonValueExpr(void)
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();
+}
+
/*
* Stuff from pathnodes.h.
*
@@ -3025,6 +3045,8 @@ parseNodeString(void)
return_value = _readJsonReturning();
else if (MATCH("JSONVALUEEXPR", 13))
return_value = _readJsonValueExpr();
+ else if (MATCH("JSONCTOREXPR", 12))
+ return_value = _readJsonConstructorExpr();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 5e2754e789..bac86aad45 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -50,6 +50,8 @@
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
@@ -382,6 +384,27 @@ contain_mutable_functions_walker(Node *node, void *context)
context))
return true;
+ if (IsA(node, JsonConstructorExpr))
+ {
+ const JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+ ListCell *lc;
+ bool is_jsonb =
+ ctor->returning->format->format_type == 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, SQLValueFunction))
{
/* all variants of SQLValueFunction are stable */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0192321d9a..406933da18 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -638,11 +638,31 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> json_format_clause_opt
json_representation
json_value_expr
+ json_func_expr
json_output_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
+
+%type <list> json_name_and_value_list
+ json_value_expr_list
+ json_array_aggregate_order_by_clause_opt
%type <ival> json_encoding
json_encoding_clause_opt
+%type <boolean> json_key_uniqueness_constraint_opt
+ json_object_constructor_null_clause_opt
+ json_array_constructor_null_clause_opt
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
* They must be listed first so that their numeric codes do not depend on
@@ -668,7 +688,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
@@ -705,9 +725,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
- JOIN JSON
+ JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG
- KEY
+ KEY KEYS
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
@@ -771,7 +791,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
@@ -825,11 +845,13 @@ 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 ABSENT UNIQUE
%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 ] */
/* Unary Operators */
%left AT /* sets precedence for AT TIME ZONE */
%left COLLATE
@@ -847,6 +869,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
+
%%
/*
@@ -13372,7 +13397,7 @@ ConstInterval:
opt_timezone:
WITH_LA TIME ZONE { $$ = true; }
- | WITHOUT TIME ZONE { $$ = false; }
+ | WITHOUT_LA TIME ZONE { $$ = false; }
| /*EMPTY*/ { $$ = false; }
;
@@ -13985,6 +14010,17 @@ b_expr: c_expr
}
;
+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.
*
@@ -14237,6 +14273,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; }
;
@@ -14250,6 +14295,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; }
;
/*
@@ -14537,6 +14583,8 @@ func_expr_common_subexpr:
n->location = @1;
$$ = (Node *)n;
}
+ | json_func_expr
+ { $$ = $1; }
;
/*
@@ -15236,11 +15284,14 @@ opt_asymmetric: ASYMMETRIC
;
/* SQL/JSON support */
+json_func_expr:
+ json_value_constructor
+ ;
json_value_expr:
a_expr json_format_clause_opt
{
- $$ = (Node *) makeJsonValueExpr((Expr *) $1, $2);
+ $$ = (Node *) makeJsonValueExpr((Expr *) $1, castNode(JsonFormat, $2));
}
;
@@ -15248,7 +15299,7 @@ json_format_clause_opt:
FORMAT json_representation
{
$$ = $2;
- $$.location = @1;
+ castNode(JsonFormat, $$)->location = @1;
}
| /* EMPTY */
{
@@ -15278,10 +15329,196 @@ json_output_clause_opt:
{
JsonOutput *n = makeNode(JsonOutput);
n->typeName = $2;
- n->returning.format = $3;
+ n->returning = makeNode(JsonReturning);
+ n->returning->format = (JsonFormat *) $3;
$$ = (Node *) n;
}
| /* EMPTY */ { $$ = NULL; }
+ ;
+
+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; }
;
/*****************************************************************************
@@ -15728,6 +15965,7 @@ BareColLabel: IDENT { $$ = $1; }
*/
unreserved_keyword:
ABORT_P
+ | ABSENT
| ABSOLUTE_P
| ACCESS
| ACTION
@@ -15858,6 +16096,7 @@ unreserved_keyword:
| ISOLATION
| JSON
| KEY
+ | KEYS
| LABEL
| LANGUAGE
| LARGE_P
@@ -16066,6 +16305,10 @@ col_name_keyword:
| INT_P
| INTEGER
| INTERVAL
+ | JSON_ARRAY
+ | JSON_ARRAYAGG
+ | JSON_OBJECT
+ | JSON_OBJECTAGG
| LEAST
| NATIONAL
| NCHAR
@@ -16234,6 +16477,7 @@ reserved_keyword:
*/
bare_label_keyword:
ABORT_P
+ | ABSENT
| ABSOLUTE_P
| ACCESS
| ACTION
@@ -16419,7 +16663,12 @@ bare_label_keyword:
| ISOLATION
| JOIN
| JSON
+ | JSON_ARRAY
+ | JSON_ARRAYAGG
+ | JSON_OBJECT
+ | JSON_OBJECTAGG
| KEY
+ | KEYS
| LABEL
| LANGUAGE
| LARGE_P
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 985ddbedf1..6b93a76bca 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"
@@ -75,6 +77,14 @@ static Node *transformWholeRowRef(ParseState *pstate,
static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformJsonObjectConstructor(ParseState *pstate,
+ JsonObjectConstructor *ctor);
+static Node *transformJsonArrayConstructor(ParseState *pstate,
+ JsonArrayConstructor *ctor);
+static Node *transformJsonArrayQueryConstructor(ParseState *pstate,
+ JsonArrayQueryConstructor *ctor);
+static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg);
+static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg);
static Node *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -302,6 +312,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
break;
}
+ case T_JsonObjectConstructor:
+ result = transformJsonObjectConstructor(pstate, (JsonObjectConstructor *) expr);
+ break;
+
+ case T_JsonArrayConstructor:
+ result = transformJsonArrayConstructor(pstate, (JsonArrayConstructor *) expr);
+ break;
+
+ case T_JsonArrayQueryConstructor:
+ result = transformJsonArrayQueryConstructor(pstate, (JsonArrayQueryConstructor *) expr);
+ break;
+
+ case T_JsonObjectAgg:
+ result = transformJsonObjectAgg(pstate, (JsonObjectAgg *) expr);
+ break;
+
+ case T_JsonArrayAgg:
+ result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3280,3 +3310,562 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
return expr;
}
+
+/*
+ * Checks specified output format for its applicability to the target type.
+ */
+static void
+checkJsonOutputFormat(ParseState *pstate, const JsonFormat *format,
+ Oid targettype, bool allow_format_for_non_strings)
+{
+ if (!allow_format_for_non_strings &&
+ format->format_type != JS_FORMAT_DEFAULT &&
+ (targettype != BYTEAOID &&
+ targettype != JSONOID &&
+ targettype != JSONBOID))
+ {
+ char typcategory;
+ bool typispreferred;
+
+ get_type_category_preferred(targettype, &typcategory, &typispreferred);
+
+ if (typcategory != TYPCATEGORY_STRING)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ parser_errposition(pstate, format->location),
+ errmsg("cannot use JSON format with non-string output types")));
+ }
+
+ if (format->format_type == JS_FORMAT_JSON)
+ {
+ JsonEncoding enc = format->encoding != JS_ENC_DEFAULT ?
+ format->encoding : JS_ENC_UTF8;
+
+ if (targettype != BYTEAOID &&
+ format->encoding != JS_ENC_DEFAULT)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ parser_errposition(pstate, format->location),
+ errmsg("cannot set JSON encoding for non-bytea output types")));
+
+ if (enc != JS_ENC_UTF8)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported JSON encoding"),
+ errhint("only UTF8 JSON encoding is supported"),
+ parser_errposition(pstate, format->location)));
+ }
+}
+
+/*
+ * Transform JSON output clause.
+ *
+ * Assigns target type oid and modifier.
+ * Assigns default format or checks specified format for its applicability to
+ * the target type.
+ */
+static JsonReturning *
+transformJsonOutput(ParseState *pstate, const JsonOutput *output,
+ bool allow_format)
+{
+ JsonReturning *ret;
+
+ /* if output clause is not specified, make default clause value */
+ if (!output)
+ {
+ ret = makeNode(JsonReturning);
+
+ ret->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+ ret->typid = InvalidOid;
+ ret->typmod = -1;
+
+ return ret;
+ }
+
+ ret = copyObject(output->returning);
+
+ typenameTypeIdAndMod(pstate, output->typeName, &ret->typid, &ret->typmod);
+
+ if (output->typeName->setof)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning SETOF types is not supported in SQL/JSON functions")));
+
+ if (ret->format->format_type == JS_FORMAT_DEFAULT)
+ /* assign JSONB format when returning jsonb, or JSON format otherwise */
+ ret->format->format_type =
+ ret->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON;
+ else
+ checkJsonOutputFormat(pstate, ret->format, ret->typid, allow_format);
+
+ return ret;
+}
+
+/*
+ * Transform JSON output clause of JSON 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_type = JS_FORMAT_JSONB;
+ }
+ else
+ {
+ /* Note: this includes the have_json case */
+
+ /* XXX TEXT is default by the standard, but we return JSON */
+ returning->typid = JSONOID;
+ returning->format->format_type = JS_FORMAT_JSON;
+ }
+
+ returning->typmod = -1;
+ }
+
+ return returning;
+}
+
+/*
+ * Coerce json[b]-valued function expression to the output type.
+ */
+static Node *
+coerceJsonFuncExpr(ParseState *pstate, Node *expr,
+ const JsonReturning *returning, bool report_error)
+{
+ Node *res;
+ int location;
+ Oid exprtype = exprType(expr);
+
+ /* if output type is not specified or equals to function type, return */
+ if (!OidIsValid(returning->typid) || returning->typid == exprtype)
+ return expr;
+
+ location = exprLocation(expr);
+
+ if (location < 0)
+ location = returning ? returning->format->location : -1;
+
+ /* special case for RETURNING bytea FORMAT json */
+ if (returning->format->format_type == JS_FORMAT_JSON &&
+ returning->typid == BYTEAOID)
+ {
+ /* encode json text into bytea using pg_convert_to() */
+ Node *texpr = coerce_to_specific_type(pstate, expr, TEXTOID,
+ "JSON_FUNCTION");
+ Const *enc = getJsonEncodingConst(returning->format);
+ FuncExpr *fexpr = makeFuncExpr(F_CONVERT_TO, BYTEAOID,
+ list_make2(texpr, enc),
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+ fexpr->location = location;
+
+ return (Node *) fexpr;
+ }
+
+ /* try to coerce expression to the output type */
+ res = coerce_to_target_type(pstate, expr, exprtype,
+ returning->typid, returning->typmod,
+ /* XXX throwing errors when casting to char(N) */
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+
+ if (!res && report_error)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprtype),
+ format_type_be(returning->typid)),
+ parser_coercion_errposition(pstate, location, expr)));
+
+ return res;
+}
+
+static Node *
+makeJsonConstructorExpr(ParseState *pstate, JsonConstructorType type,
+ List *args, Expr *fexpr, JsonReturning *returning,
+ bool unique, bool absent_on_null, int location)
+{
+ JsonConstructorExpr *jsctor = makeNode(JsonConstructorExpr);
+ Node *placeholder;
+ Node *coercion;
+ Oid intermediate_typid =
+ returning->format->format_type == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
+
+ jsctor->args = args;
+ jsctor->func = fexpr;
+ jsctor->type = type;
+ jsctor->returning = returning;
+ jsctor->unique = unique;
+ jsctor->absent_on_null = absent_on_null;
+ jsctor->location = location;
+
+ if (fexpr)
+ placeholder = makeCaseTestExpr((Node *) fexpr);
+ else
+ {
+ CaseTestExpr *cte = makeNode(CaseTestExpr);
+
+ cte->typeId = intermediate_typid;
+ cte->typeMod = -1;
+ cte->collation = InvalidOid;
+
+ placeholder = (Node *) cte;
+ }
+
+ coercion = coerceJsonFuncExpr(pstate, placeholder, returning, true);
+
+ if (coercion != placeholder)
+ jsctor->coercion = (Expr *) coercion;
+
+ return (Node *) jsctor;
+}
+
+/*
+ * Transform JSON_OBJECT() constructor.
+ *
+ * JSON_OBJECT() is transformed into json[b]_build_object[_ext]() call
+ * depending on the output JSON format. The first two arguments of
+ * json[b]_build_object_ext() are absent_on_null and check_key_uniqueness.
+ *
+ * Then function call result is coerced to the target type.
+ */
+static Node *
+transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor)
+{
+ JsonReturning *returning;
+ List *args = NIL;
+
+ /* transform key-value pairs, if any */
+ if (ctor->exprs)
+ {
+ ListCell *lc;
+
+ /* transform and append key-value arguments */
+ foreach(lc, ctor->exprs)
+ {
+ JsonKeyValue *kv = castNode(JsonKeyValue, lfirst(lc));
+ Node *key = transformExprRecurse(pstate, (Node *) kv->key);
+ Node *val = transformJsonValueExpr(pstate, kv->value,
+ JS_FORMAT_DEFAULT);
+
+ args = lappend(args, key);
+ args = lappend(args, val);
+ }
+ }
+
+ returning = transformJsonConstructorOutput(pstate, ctor->output, args);
+
+ return makeJsonConstructorExpr(pstate, JSCTOR_JSON_OBJECT, args, NULL,
+ returning, ctor->unique,
+ ctor->absent_on_null, ctor->location);
+}
+
+/*
+ * Transform JSON_ARRAY(query [FORMAT] [RETURNING] [ON NULL]) into
+ * (SELECT JSON_ARRAYAGG(a [FORMAT] [RETURNING] [ON NULL]) FROM (query) q(a))
+ */
+static Node *
+transformJsonArrayQueryConstructor(ParseState *pstate,
+ JsonArrayQueryConstructor *ctor)
+{
+ SubLink *sublink = makeNode(SubLink);
+ SelectStmt *select = makeNode(SelectStmt);
+ RangeSubselect *range = makeNode(RangeSubselect);
+ Alias *alias = makeNode(Alias);
+ ResTarget *target = makeNode(ResTarget);
+ JsonArrayAgg *agg = makeNode(JsonArrayAgg);
+ ColumnRef *colref = makeNode(ColumnRef);
+ Query *query;
+ ParseState *qpstate;
+
+ /* Transform query only for counting target list entries. */
+ qpstate = make_parsestate(pstate);
+
+ query = transformStmt(qpstate, ctor->query);
+
+ if (count_nonjunk_tlist_entries(query->targetList) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("subquery must return only one column"),
+ parser_errposition(pstate, ctor->location)));
+
+ free_parsestate(qpstate);
+
+ colref->fields = list_make2(makeString(pstrdup("q")),
+ makeString(pstrdup("a")));
+ colref->location = ctor->location;
+
+ agg->arg = makeJsonValueExpr((Expr *) colref, ctor->format);
+ agg->absent_on_null = ctor->absent_on_null;
+ agg->constructor = makeNode(JsonAggConstructor);
+ agg->constructor->agg_order = NIL;
+ agg->constructor->output = ctor->output;
+ agg->constructor->location = ctor->location;
+
+ target->name = NULL;
+ target->indirection = NIL;
+ target->val = (Node *) agg;
+ target->location = ctor->location;
+
+ alias->aliasname = pstrdup("q");
+ alias->colnames = list_make1(makeString(pstrdup("a")));
+
+ range->lateral = false;
+ range->subquery = ctor->query;
+ range->alias = alias;
+
+ select->targetList = list_make1(target);
+ select->fromClause = list_make1(range);
+
+ sublink->subLinkType = EXPR_SUBLINK;
+ sublink->subLinkId = 0;
+ sublink->testexpr = NULL;
+ sublink->operName = NIL;
+ sublink->subselect = (Node *) select;
+ sublink->location = ctor->location;
+
+ return transformExprRecurse(pstate, (Node *) sublink);
+}
+
+/*
+ * Common code for JSON_OBJECTAGG and JSON_ARRAYAGG transformation.
+ */
+static Node *
+transformJsonAggConstructor(ParseState *pstate, JsonAggConstructor *agg_ctor,
+ JsonReturning *returning, List *args,
+ const char *aggfn, Oid aggtype,
+ JsonConstructorType ctor_type,
+ bool unique, bool absent_on_null)
+{
+ Oid aggfnoid;
+ Node *node;
+ Expr *aggfilter = agg_ctor->agg_filter ? (Expr *)
+ transformWhereClause(pstate, agg_ctor->agg_filter,
+ EXPR_KIND_FILTER, "FILTER") : NULL;
+
+ aggfnoid = DatumGetInt32(DirectFunctionCall1(regprocin,
+ CStringGetDatum(aggfn)));
+
+ if (agg_ctor->over)
+ {
+ /* window function */
+ WindowFunc *wfunc = makeNode(WindowFunc);
+
+ wfunc->winfnoid = aggfnoid;
+ wfunc->wintype = aggtype;
+ /* wincollid and inputcollid will be set by parse_collate.c */
+ wfunc->args = args;
+ /* winref will be set by transformWindowFuncCall */
+ wfunc->winstar = false;
+ wfunc->winagg = true;
+ wfunc->aggfilter = aggfilter;
+ wfunc->location = agg_ctor->location;
+
+ /*
+ * ordered aggs not allowed in windows yet
+ */
+ if (agg_ctor->agg_order != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("aggregate ORDER BY is not implemented for window functions"),
+ parser_errposition(pstate, agg_ctor->location)));
+
+ /* parse_agg.c does additional window-func-specific processing */
+ transformWindowFuncCall(pstate, wfunc, agg_ctor->over);
+
+ node = (Node *) wfunc;
+ }
+ else
+ {
+ Aggref *aggref = makeNode(Aggref);
+
+ aggref->aggfnoid = aggfnoid;
+ aggref->aggtype = aggtype;
+
+ /* aggcollid and inputcollid will be set by parse_collate.c */
+ aggref->aggtranstype = InvalidOid; /* will be set by planner */
+ /* aggargtypes will be set by transformAggregateCall */
+ /* aggdirectargs and args will be set by transformAggregateCall */
+ /* aggorder and aggdistinct will be set by transformAggregateCall */
+ aggref->aggfilter = aggfilter;
+ aggref->aggstar = false;
+ aggref->aggvariadic = false;
+ aggref->aggkind = AGGKIND_NORMAL;
+ /* agglevelsup will be set by transformAggregateCall */
+ aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */
+ aggref->location = agg_ctor->location;
+
+ transformAggregateCall(pstate, aggref, args, agg_ctor->agg_order, false);
+
+ node = (Node *) aggref;
+ }
+
+ return makeJsonConstructorExpr(pstate, ctor_type, NIL, (Expr *) node,
+ returning, unique, absent_on_null,
+ agg_ctor->location);
+}
+
+/*
+ * Transform JSON_OBJECTAGG() aggregate function.
+ *
+ * JSON_OBJECTAGG() is transformed into
+ * json[b]_objectagg(key, value, absent_on_null, check_unique) call depending on
+ * the output JSON format. Then the function call result is coerced to the
+ * target output type.
+ */
+static Node *
+transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg)
+{
+ JsonReturning *returning;
+ Node *key;
+ Node *val;
+ List *args;
+ const char *aggfnname;
+ Oid aggtype;
+
+ key = transformExprRecurse(pstate, (Node *) agg->arg->key);
+ val = transformJsonValueExpr(pstate, agg->arg->value, JS_FORMAT_DEFAULT);
+ args = list_make2(key, val);
+
+ returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
+ args);
+
+ if (returning->format->format_type == JS_FORMAT_JSONB)
+ {
+ if (agg->absent_on_null)
+ if (agg->unique)
+ aggfnname = "pg_catalog.jsonb_object_agg_unique_strict"; /* 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 = transformJsonValueExpr(pstate, agg->arg, JS_FORMAT_DEFAULT);
+
+ returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
+ list_make1(arg));
+
+ if (returning->format->format_type == JS_FORMAT_JSONB)
+ {
+ aggfnname = agg->absent_on_null ?
+ "pg_catalog.jsonb_agg_strict" : "pg_catalog.jsonb_agg";
+ aggtype = JSONBOID;
+ }
+ else
+ {
+ aggfnname = agg->absent_on_null ?
+ "pg_catalog.json_agg_strict" : "pg_catalog.json_agg";
+ aggtype = JSONOID;
+ }
+
+ return transformJsonAggConstructor(pstate, agg->constructor, returning,
+ list_make1(arg), aggfnname, aggtype,
+ JSCTOR_JSON_ARRAYAGG,
+ false, agg->absent_on_null);
+}
+
+/*
+ * Transform JSON_ARRAY() constructor.
+ *
+ * JSON_ARRAY() is transformed into json[b]_build_array[_ext]() call
+ * depending on the output JSON format. The first argument of
+ * json[b]_build_array_ext() is absent_on_null.
+ *
+ * Then function call result is coerced to the target type.
+ */
+static Node *
+transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor)
+{
+ JsonReturning *returning;
+ List *args = NIL;
+
+ /* transform element expressions, if any */
+ if (ctor->exprs)
+ {
+ ListCell *lc;
+
+ /* transform and append element arguments */
+ foreach(lc, ctor->exprs)
+ {
+ JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc));
+ Node *val = transformJsonValueExpr(pstate, jsval,
+ JS_FORMAT_DEFAULT);
+
+ args = lappend(args, val);
+ }
+ }
+
+ returning = transformJsonConstructorOutput(pstate, ctor->output, args);
+
+ return makeJsonConstructorExpr(pstate, JSCTOR_JSON_ARRAY, args, NULL,
+ returning, false, ctor->absent_on_null,
+ ctor->location);
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 059eeb9e94..204d285773 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1957,6 +1957,19 @@ FigureColnameInternal(Node *node, char **name)
case T_XmlSerialize:
*name = "xmlserialize";
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;
default:
break;
}
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 50227cc098..eee0a29c08 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/json.c b/src/backend/utils/adt/json.c
index 7879f342e6..d088fafc56 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -13,7 +13,9 @@
*/
#include "postgres.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "common/hashfn.h"
#include "funcapi.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
@@ -42,6 +44,42 @@ typedef enum /* type categories for datum_to_json */
JSONTYPE_OTHER /* all else */
} JsonTypeCategory;
+/* 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
+{
+ 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
{
StringInfo str;
@@ -49,6 +87,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,
@@ -722,6 +761,38 @@ row_to_json_pretty(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(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)
*/
@@ -754,8 +825,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 +866,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 +885,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 +903,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 +945,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 +1081,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 +1112,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
else
{
state = (JsonAggState *) PG_GETARG_POINTER(0);
- appendStringInfoString(state->str, ", ");
}
/*
@@ -925,11 +1127,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 +1183,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 +1236,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 +1262,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 +1283,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 +1344,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 +1378,13 @@ json_build_object_noargs(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
}
-/*
- * SQL function json_build_array(variadic "any")
- */
Datum
-json_build_array(PG_FUNCTION_ARGS)
+json_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types,
+ bool absent_on_null)
{
- int nargs;
int i;
const char *sep = "";
StringInfo result;
- Datum *args;
- bool *nulls;
- Oid *types;
-
- /* fetch argument values to build the array */
- nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls);
-
- if (nargs < 0)
- PG_RETURN_NULL();
result = makeStringInfo();
@@ -1076,6 +1392,9 @@ json_build_array(PG_FUNCTION_ARGS)
for (i = 0; i < nargs; i++)
{
+ if (absent_on_null && nulls[i])
+ continue;
+
appendStringInfoString(result, sep);
sep = ", ";
add_json(args[i], nulls[i], result, types[i], false);
@@ -1083,7 +1402,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));
}
/*
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index f5f40a94bd..a103cbc7c6 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"
@@ -1126,6 +1127,39 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result,
datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar);
}
+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)
*/
@@ -1153,24 +1187,12 @@ to_jsonb(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(JsonbValueToJsonb(result.res));
}
-/*
- * SQL function jsonb_build_object(variadic "any")
- */
Datum
-jsonb_build_object(PG_FUNCTION_ARGS)
+jsonb_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types,
+ bool absent_on_null, bool unique_keys)
{
- int nargs;
int i;
JsonbInState result;
- Datum *args;
- bool *nulls;
- Oid *types;
-
- /* build argument values to build the object */
- nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
-
- if (nargs < 0)
- PG_RETURN_NULL();
if (nargs % 2 != 0)
ereport(ERROR,
@@ -1183,15 +1205,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 +1233,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 +1271,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 +1555,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 +1568,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 +1617,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 +1689,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 +1739,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 +1755,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
*jbval;
JsonbValue v;
JsonbIteratorToken type;
+ bool skip;
if (!AggCheckCallContext(fcinfo, &aggcontext))
{
@@ -1692,6 +1775,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 +1813,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 +1877,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 +1959,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)
{
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 60442758b3..aa151a53d6 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -64,7 +64,8 @@ static int lengthCompareJsonbStringValue(const void *a, const void *b);
static int lengthCompareJsonbString(const char *val1, int len1,
const char *val2, int len2);
static int lengthCompareJsonbPair(const void *a, const void *b, void *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);
@@ -689,7 +690,9 @@ pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq,
appendElement(*pstate, scalarVal);
break;
case WJB_END_OBJECT:
- uniqueifyJsonbObject(&(*pstate)->contVal);
+ uniqueifyJsonbObject(&(*pstate)->contVal,
+ (*pstate)->unique_keys,
+ (*pstate)->skip_nulls);
/* fall through! */
case WJB_END_ARRAY:
/* Steps here common to WJB_END_OBJECT case */
@@ -732,6 +735,9 @@ pushState(JsonbParseState **pstate)
JsonbParseState *ns = palloc(sizeof(JsonbParseState));
ns->next = *pstate;
+ ns->unique_keys = false;
+ ns->skip_nulls = false;
+
return ns;
}
@@ -1936,7 +1942,7 @@ lengthCompareJsonbPair(const void *a, const void *b, void *binequal)
* Sort and unique-ify pairs in JsonbValue object
*/
static void
-uniqueifyJsonbObject(JsonbValue *object)
+uniqueifyJsonbObject(JsonbValue *object, bool unique_keys, bool skip_nulls)
{
bool hasNonUniq = false;
@@ -1946,15 +1952,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/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f34f5199fa..3e5f31ba2e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -457,6 +457,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);
@@ -6245,7 +6251,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, '(');
@@ -8091,6 +8098,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_Aggref:
case T_WindowFunc:
case T_FuncExpr:
+ case T_JsonConstructorExpr:
/* function-like: name(..) or name[..] */
return true;
@@ -8376,12 +8384,12 @@ get_rule_expr_paren(Node *node, deparse_context *context,
* get_json_format - Parse back a JsonFormat node
*/
static void
-get_json_format(JsonFormat *format, deparse_context *context)
+get_json_format(JsonFormat *format, StringInfo buf)
{
if (format->format_type == JS_FORMAT_DEFAULT)
return;
- appendStringInfoString(context->buf,
+ appendStringInfoString(buf,
format->format_type == JS_FORMAT_JSONB ?
" FORMAT JSONB" : " FORMAT JSON");
@@ -8391,7 +8399,7 @@ get_json_format(JsonFormat *format, deparse_context *context)
format->encoding == JS_ENC_UTF16 ? "UTF16" :
format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8";
- appendStringInfo(context->buf, " ENCODING %s", encoding);
+ appendStringInfo(buf, " ENCODING %s", encoding);
}
}
@@ -8399,20 +8407,20 @@ get_json_format(JsonFormat *format, deparse_context *context)
* get_json_returning - Parse back a JsonReturning structure
*/
static void
-get_json_returning(JsonReturning *returning, deparse_context *context,
+get_json_returning(JsonReturning *returning, StringInfo buf,
bool json_format_by_default)
{
if (!OidIsValid(returning->typid))
return;
- appendStringInfo(context->buf, " RETURNING %s",
+ appendStringInfo(buf, " RETURNING %s",
format_type_with_typemod(returning->typid,
returning->typmod));
if (!json_format_by_default ||
returning->format->format_type !=
(returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON))
- get_json_format(returning->format, context);
+ get_json_format(returning->format, buf);
}
/* ----------
@@ -9579,10 +9587,14 @@ get_rule_expr(Node *node, deparse_context *context,
JsonValueExpr *jve = (JsonValueExpr *) node;
get_rule_expr((Node *) jve->raw_expr, context, false);
- get_json_format(jve->format, context);
+ get_json_format(jve->format, context->buf);
}
break;
+ case T_JsonConstructorExpr:
+ get_json_constructor((JsonConstructorExpr *) node, context, false);
+ break;
+
case T_List:
{
char *sep;
@@ -9851,17 +9863,89 @@ 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");
+
+ 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_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
@@ -9891,13 +9975,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))
@@ -9933,7 +10018,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);
@@ -9947,6 +10042,9 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
}
}
+ if (options)
+ appendStringInfoString(buf, options);
+
if (aggref->aggfilter != NULL)
{
appendStringInfoString(buf, ") FILTER (WHERE ");
@@ -9956,6 +10054,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
@@ -9975,10 +10083,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];
@@ -10002,16 +10112,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)
{
@@ -10048,6 +10172,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
*
@@ -10288,6 +10421,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/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index 84435420e4..d14b751058 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -763,6 +763,18 @@ JumbleExpr(JumbleState *jstate, Node *node)
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_List:
foreach(temp, (List *) node)
{
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 2843f4b415..1934f19335 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -567,14 +567,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 bf88858171..1d8b38fdcc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8737,6 +8737,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' },
@@ -8744,10 +8748,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',
@@ -8756,6 +8776,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',
@@ -9628,6 +9661,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',
@@ -9636,10 +9673,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',
@@ -9648,6 +9704,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 56a89ebafb..c830fcf726 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -239,6 +239,7 @@ typedef enum ExprEvalOp
EEOP_GROUPING_FUNC,
EEOP_WINDOW_FUNC,
EEOP_SUBPLAN,
+ EEOP_JSON_CONSTRUCTOR,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -668,6 +669,17 @@ 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;
+ int nargs;
+ } json_constructor;
+
} d;
} ExprEvalStep;
@@ -769,6 +781,8 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
+extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index ec8b71a685..e50b933288 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -109,6 +109,7 @@ extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_
extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding,
int location);
extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format);
+extern Node *makeJsonKeyValue(Node *key, Node *value);
extern JsonEncoding makeJsonEncoding(char *name);
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 59737f1034..05f0b79e82 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -204,6 +204,7 @@ typedef enum NodeTag
T_JsonFormat,
T_JsonReturning,
T_JsonValueExpr,
+ T_JsonConstructorExpr,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -494,6 +495,13 @@ typedef enum NodeTag
T_VacuumRelation,
T_PublicationObjSpec,
T_PublicationTable,
+ T_JsonObjectConstructor,
+ T_JsonArrayConstructor,
+ T_JsonArrayQueryConstructor,
+ T_JsonAggConstructor,
+ T_JsonObjectAgg,
+ T_JsonArrayAgg,
+ T_JsonKeyValue,
T_JsonOutput,
/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b9a8f9af76..40fb034028 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1561,9 +1561,103 @@ typedef struct JsonOutput
{
NodeTag type;
TypeName *typeName; /* RETURNING type name, if specified */
- JsonReturning returning; /* RETURNING FORMAT clause and type Oids */
+ JsonReturning *returning; /* RETURNING FORMAT clause and type Oids */
} JsonOutput;
+/*
+ * JsonKeyValue -
+ * untransformed representation of JSON object key-value pair for
+ * JSON_OBJECT() and JSON_OBJECTAGG()
+ */
+typedef struct JsonKeyValue
+{
+ NodeTag type;
+ Expr *key; /* key expression */
+ JsonValueExpr *value; /* JSON value expression */
+} JsonKeyValue;
+
+/*
+ * JsonObjectConstructor -
+ * untransformed representation of JSON_OBJECT() constructor
+ */
+typedef struct JsonObjectConstructor
+{
+ NodeTag type;
+ List *exprs; /* list of JsonKeyValue pairs */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ bool absent_on_null; /* skip NULL values? */
+ bool unique; /* check key uniqueness? */
+ int location; /* token location, or -1 if unknown */
+} JsonObjectConstructor;
+
+/*
+ * JsonArrayConstructor -
+ * untransformed representation of JSON_ARRAY(element,...) constructor
+ */
+typedef struct JsonArrayConstructor
+{
+ NodeTag type;
+ List *exprs; /* list of JsonValueExpr elements */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ bool absent_on_null; /* skip NULL elements? */
+ int location; /* token location, or -1 if unknown */
+} JsonArrayConstructor;
+
+/*
+ * JsonArrayQueryConstructor -
+ * untransformed representation of JSON_ARRAY(subquery) constructor
+ */
+typedef struct JsonArrayQueryConstructor
+{
+ NodeTag type;
+ Node *query; /* subquery */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ JsonFormat *format; /* FORMAT clause for subquery, if specified */
+ bool absent_on_null; /* skip NULL elements? */
+ int location; /* token location, or -1 if unknown */
+} JsonArrayQueryConstructor;
+
+/*
+ * JsonAggConstructor -
+ * common fields of untransformed representation of
+ * JSON_ARRAYAGG() and JSON_OBJECTAGG()
+ */
+typedef struct JsonAggConstructor
+{
+ NodeTag type;
+ JsonOutput *output; /* RETURNING clause, if any */
+ Node *agg_filter; /* FILTER clause, if any */
+ List *agg_order; /* ORDER BY clause, if any */
+ struct WindowDef *over; /* OVER clause, if any */
+ int location; /* token location, or -1 if unknown */
+} JsonAggConstructor;
+
+/*
+ * JsonObjectAgg -
+ * untransformed representation of JSON_OBJECTAGG()
+ */
+typedef struct JsonObjectAgg
+{
+ NodeTag type;
+ JsonAggConstructor *constructor; /* common fields */
+ JsonKeyValue *arg; /* object key-value pair */
+ bool absent_on_null; /* skip NULL values? */
+ bool unique; /* check key uniqueness? */
+} JsonObjectAgg;
+
+/*
+ * JsonArrayAgg -
+ * untransformed representation of JSON_ARRRAYAGG()
+ */
+typedef struct JsonArrayAgg
+{
+ NodeTag type;
+ JsonAggConstructor *constructor; /* common fields */
+ JsonValueExpr *arg; /* array element expression */
+ bool absent_on_null; /* skip NULL elements? */
+} JsonArrayAgg;
+
+
/*****************************************************************************
* Raw Grammar Output Statements
*****************************************************************************/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 118a0d6b49..20e101e8bd 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1294,6 +1294,31 @@ typedef struct JsonValueExpr
JsonFormat *format; /* FORMAT clause, if specified */
} JsonValueExpr;
+typedef enum JsonConstructorType
+{
+ JSCTOR_JSON_OBJECT = 1,
+ JSCTOR_JSON_ARRAY = 2,
+ JSCTOR_JSON_OBJECTAGG = 3,
+ JSCTOR_JSON_ARRAYAGG = 4
+} JsonConstructorType;
+
+/*
+ * JsonConstructorExpr -
+ * wrapper over FuncExpr/Aggref/WindowFunc for SQL/JSON constructors
+ */
+typedef struct JsonConstructorExpr
+{
+ Expr xpr;
+ JsonConstructorType type; /* constructor type */
+ List *args;
+ Expr *func; /* underlying json[b]_xxx() function call */
+ Expr *coercion; /* coercion to RETURNING type */
+ JsonReturning *returning; /* RETURNING clause */
+ bool absent_on_null; /* ABSENT ON NULL? */
+ bool unique; /* WITH UNIQUE KEYS? (JSON_OBJECT[AGG] only) */
+ int location;
+} JsonConstructorExpr;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f3502b8be4..f44440d4a9 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)
@@ -229,7 +230,12 @@ PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL)
PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index 8a84a0cdb4..63d83b815f 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -20,5 +20,11 @@
extern void escape_json(StringInfo buf, const char *str);
extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
const int *tzp);
+extern bool to_json_is_immutable(Oid typoid);
+extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null,
+ bool unique_keys);
+extern Datum json_build_array_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null);
#endif /* JSON_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 4cbe6edf21..6bcf35dd0a 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;
/*
@@ -412,4 +414,11 @@ extern Datum jsonb_set_element(Jsonb *jb, Datum *path, int path_len,
JsonbValue *newval);
extern Datum jsonb_get_element(Jsonb *jb, Datum *path, int npath,
bool *isnull, bool as_text);
+extern bool to_jsonb_is_immutable(Oid typoid);
+extern Datum jsonb_build_object_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null,
+ bool unique_keys);
+extern Datum jsonb_build_array_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null);
+
#endif /* __JSONB_H__ */
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index dee6b8200d..5ec511fd01 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 a44e07a17a..5e2b606f9b 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/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 4ce6c039b4..15e4016836 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..7dca5a8a30
--- /dev/null
+++ b/src/test/regress/expected/sqljson.out
@@ -0,0 +1,746 @@
+-- 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;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 6d8f524ae9..3ce701a588 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
# ----------
# Another group of parallel tests (JSON related)
# ----------
-test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath
+test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 2b292851e3..63fe114fed 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..aaef2d8aab
--- /dev/null
+++ b/src/test/regress/sql/sqljson.sql
@@ -0,0 +1,282 @@
+-- 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;
--
2.25.1
[text/x-patch] 0003-IS-JSON-predicate-v64.patch (54.5K, ../../[email protected]/4-0003-IS-JSON-predicate-v64.patch)
download | inline diff:
From fd7c3c6ad4dc8e85fad9b3a57059c24a5387cf3e Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:02:53 -0500
Subject: [PATCH 3/6] IS JSON predicate
---
doc/src/sgml/func.sgml | 251 +++++++++++++++++++++++++-
src/backend/executor/execExpr.c | 13 ++
src/backend/executor/execExprInterp.c | 95 ++++++++++
src/backend/jit/llvm/llvmjit_expr.c | 6 +
src/backend/jit/llvm/llvmjit_types.c | 1 +
src/backend/nodes/copyfuncs.c | 20 ++
src/backend/nodes/equalfuncs.c | 15 ++
src/backend/nodes/makefuncs.c | 19 ++
src/backend/nodes/nodeFuncs.c | 26 +++
src/backend/nodes/outfuncs.c | 14 ++
src/backend/nodes/readfuncs.c | 18 ++
src/backend/parser/gram.y | 63 ++++++-
src/backend/parser/parse_expr.c | 76 ++++++++
src/backend/utils/adt/json.c | 105 +++++++++--
src/backend/utils/adt/jsonfuncs.c | 20 ++
src/backend/utils/adt/ruleutils.c | 35 ++++
src/backend/utils/misc/queryjumble.c | 10 +
src/include/executor/execExpr.h | 8 +
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/primnodes.h | 26 +++
src/include/parser/kwlist.h | 1 +
src/include/utils/json.h | 1 +
src/include/utils/jsonfuncs.h | 3 +
src/test/regress/expected/sqljson.out | 198 ++++++++++++++++++++
src/test/regress/sql/sqljson.sql | 96 ++++++++++
26 files changed, 1106 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index faf0c92f76..7a35a44075 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17606,7 +17606,16 @@ $.* ? (@ like_regex "^\\d+$")
</listitem>
</itemizedlist>
- <sect3 id="functions-sqljson-producing">
+ <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>
@@ -18366,10 +18375,250 @@ FROM films AS f;
</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>
+ </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-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="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>
+[ PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } [, ...]]</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>
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 31e628b098..95a3787130 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2513,6 +2513,19 @@ ExecInitExprRec(Expr *node, ExprState *state,
}
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;
+ }
+
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 880cdc8e01..438d482b1c 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -73,6 +73,7 @@
#include "utils/expandedrecord.h"
#include "utils/json.h"
#include "utils/jsonb.h"
+#include "utils/jsonfuncs.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -480,6 +481,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_WINDOW_FUNC,
&&CASE_EEOP_SUBPLAN,
&&CASE_EEOP_JSON_CONSTRUCTOR,
+ &&CASE_EEOP_IS_JSON,
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
&&CASE_EEOP_AGG_DESERIALIZE,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1799,6 +1801,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_IS_JSON)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalJsonIsPredicate(state, op);
+
+ EEO_NEXT();
+ }
+
EEO_CASE(EEOP_LAST)
{
/* unreachable */
@@ -3909,6 +3919,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);
+ }
+ 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
*
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index d0c26cf58b..02511c6aec 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2354,6 +2354,12 @@ llvm_compile_expr(ExprState *state)
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_LAST:
Assert(false);
break;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 53c75dd9d6..4d7029a27f 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -132,6 +132,7 @@ void *referenced_functions[] =
ExecEvalWholeRowVar,
ExecEvalXmlExpr,
ExecEvalJsonConstructor,
+ ExecEvalJsonIsPredicate,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d89596a74a..ce3102a452 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2490,6 +2490,23 @@ _copyJsonArrayQueryConstructor(const JsonArrayQueryConstructor *from)
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;
+}
+
/* ****************************************************************
* pathnodes.h copy functions
*
@@ -5578,6 +5595,9 @@ copyObjectImpl(const void *from)
case T_JsonArrayAgg:
retval = _copyJsonArrayAgg(from);
break;
+ case T_JsonIsPredicate:
+ retval = _copyJsonIsPredicate(from);
+ break;
/*
* RELATION NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 9cb8495ddf..0fda7680b3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -976,6 +976,18 @@ _equalJsonArrayQueryConstructor(const JsonArrayQueryConstructor *a,
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;
+}
+
/*
* Stuff from pathnodes.h
*/
@@ -3505,6 +3517,9 @@ equal(const void *a, const void *b)
case T_JsonConstructorExpr:
retval = _equalJsonConstructorExpr(a, b);
break;
+ case T_JsonIsPredicate:
+ retval = _equalJsonIsPredicate(a, b);
+ break;
/*
* RELATION NODES
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 7b4f7972e6..b67e7c5297 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -887,3 +887,22 @@ makeJsonKeyValue(Node *key, Node *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 120803a841..191abcf32f 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -260,6 +260,9 @@ exprType(const Node *expr)
case T_JsonConstructorExpr:
type = ((const JsonConstructorExpr *) expr)->returning->typid;
break;
+ case T_JsonIsPredicate:
+ type = BOOLOID;
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -983,6 +986,9 @@ exprCollation(const Node *expr)
coll = InvalidOid;
}
break;
+ case T_JsonIsPredicate:
+ coll = InvalidOid; /* result is always an boolean type */
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
@@ -1209,6 +1215,9 @@ exprSetCollation(Node *expr, Oid collation)
Assert(!OidIsValid(collation)); /* result is always a json[b] type */
}
break;
+ case T_JsonIsPredicate:
+ Assert(!OidIsValid(collation)); /* result is always boolean */
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1661,6 +1670,9 @@ exprLocation(const Node *expr)
case T_JsonConstructorExpr:
loc = ((const JsonConstructorExpr *) expr)->location;
break;
+ case T_JsonIsPredicate:
+ loc = ((const JsonIsPredicate *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2417,6 +2429,8 @@ expression_tree_walker(Node *node,
return true;
}
break;
+ case T_JsonIsPredicate:
+ return walker(((JsonIsPredicate *) node)->expr, context);
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -3412,6 +3426,16 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->coercion, jve->coercion, Expr *);
MUTATE(newnode->returning, jve->returning, JsonReturning *);
+ return (Node *) newnode;
+ }
+ case T_JsonIsPredicate:
+ {
+ JsonIsPredicate *pred = (JsonIsPredicate *) node;
+ JsonIsPredicate *newnode;
+
+ FLATCOPY(newnode, pred, JsonIsPredicate);
+ MUTATE(newnode->expr, pred->expr, Node *);
+
return (Node *) newnode;
}
default:
@@ -4234,6 +4258,8 @@ raw_expression_tree_walker(Node *node,
return true;
}
break;
+ case T_JsonIsPredicate:
+ return walker(((JsonIsPredicate *) node)->expr, context);
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 c25f0bd684..ed5d159f6c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1796,6 +1796,17 @@ _outJsonConstructorExpr(StringInfo str, const JsonConstructorExpr *node)
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);
+}
+
/*****************************************************************************
*
* Stuff from pathnodes.h.
@@ -4594,6 +4605,9 @@ outNode(StringInfo str, const void *obj)
case T_JsonConstructorExpr:
_outJsonConstructorExpr(str, obj);
break;
+ case T_JsonIsPredicate:
+ _outJsonIsPredicate(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e0b3ad1ed2..aaf947b6cb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1454,6 +1454,22 @@ _readJsonConstructorExpr(void)
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.
*
@@ -3047,6 +3063,8 @@ parseNodeString(void)
return_value = _readJsonValueExpr();
else if (MATCH("JSONCTOREXPR", 12))
return_value = _readJsonConstructorExpr();
+ else if (MATCH("JSONISPREDICATE", 15))
+ return_value = _readJsonIsPredicate();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 406933da18..1d589e9b41 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -658,6 +658,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> json_encoding
json_encoding_clause_opt
+ json_predicate_type_constraint_opt
%type <boolean> json_key_uniqueness_constraint_opt
json_object_constructor_null_clause_opt
@@ -727,7 +728,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG
- KEY KEYS
+ KEY KEYS KEEP
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
@@ -755,9 +756,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
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
+ 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 STRIP_P
SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
@@ -845,13 +846,14 @@ 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 ABSENT UNIQUE
+%nonassoc ABSENT 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
@@ -13928,6 +13930,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
{
/*
@@ -14010,6 +14052,14 @@ 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; }
@@ -16197,6 +16247,7 @@ unreserved_keyword:
| ROWS
| RULE
| SAVEPOINT
+ | SCALAR
| SCHEMA
| SCHEMAS
| SCROLL
@@ -16667,6 +16718,7 @@ bare_label_keyword:
| JSON_ARRAYAGG
| JSON_OBJECT
| JSON_OBJECTAGG
+ | KEEP
| KEY
| KEYS
| LABEL
@@ -16794,6 +16846,7 @@ bare_label_keyword:
| ROWS
| RULE
| SAVEPOINT
+ | SCALAR
| SCHEMA
| SCHEMAS
| SCROLL
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6b93a76bca..0e504096fd 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -85,6 +85,7 @@ 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 *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -332,6 +333,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr);
break;
+ case T_JsonIsPredicate:
+ result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3869,3 +3874,74 @@ transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor)
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);
+}
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index d088fafc56..5edcb8bb60 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -13,6 +13,7 @@
*/
#include "postgres.h"
+#include "access/hash.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
@@ -1655,6 +1656,94 @@ 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)
+{
+ 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)
+ return false; /* invalid json */
+
+ if (check_unique_keys && !state.unique)
+ return false; /* not unique keys */
+
+ return true; /* ok */
+}
+
/*
* SQL function json_typeof(json) -> text
*
@@ -1670,21 +1759,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/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 2457061f97..03e7f10cd2 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -5647,3 +5647,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/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3e5f31ba2e..be8a75d8a7 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8192,6 +8192,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:
@@ -9595,6 +9596,40 @@ get_rule_expr(Node *node, deparse_context *context,
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_List:
{
char *sep;
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index d14b751058..8315812793 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -775,6 +775,16 @@ JumbleExpr(JumbleState *jstate, Node *node)
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_List:
foreach(temp, (List *) node)
{
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index c830fcf726..a41722ae1e 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -240,6 +240,7 @@ typedef enum ExprEvalOp
EEOP_WINDOW_FUNC,
EEOP_SUBPLAN,
EEOP_JSON_CONSTRUCTOR,
+ EEOP_IS_JSON,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -680,6 +681,12 @@ typedef struct ExprEvalStep
int nargs;
} json_constructor;
+ /* for EEOP_IS_JSON */
+ struct
+ {
+ JsonIsPredicate *pred; /* original expression node */
+ } is_json;
+
} d;
} ExprEvalStep;
@@ -774,6 +781,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);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index e50b933288..380940968b 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -110,6 +110,9 @@ extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding,
int location);
extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format);
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 05f0b79e82..666b45c5da 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -501,6 +501,7 @@ typedef enum NodeTag
T_JsonAggConstructor,
T_JsonObjectAgg,
T_JsonArrayAgg,
+ T_JsonIsPredicate,
T_JsonKeyValue,
T_JsonOutput,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 20e101e8bd..3c4c248e29 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1319,6 +1319,32 @@ typedef struct JsonConstructorExpr
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;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f44440d4a9..1726d73da6 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -372,6 +372,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)
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index 63d83b815f..bfe5b21591 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -26,5 +26,6 @@ extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls,
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);
#endif /* JSON_H */
diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h
index 865b2ff7c1..cd16b6c0c8 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);
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 7dca5a8a30..27dca7815a 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -744,3 +744,201 @@ CREATE OR REPLACE VIEW public.json_array_subquery_view AS
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/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
index aaef2d8aab..4f3c06dcb3 100644
--- a/src/test/regress/sql/sqljson.sql
+++ b/src/test/regress/sql/sqljson.sql
@@ -280,3 +280,99 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING
\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.1
[text/x-patch] 0004-SQL-JSON-query-functions-v64.patch (195.7K, ../../[email protected]/5-0004-SQL-JSON-query-functions-v64.patch)
download | inline diff:
From f5e8ad6b35870c8599482c0e5911360e75676769 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:11:14 -0500
Subject: [PATCH 4/6] SQL/JSON query functions
---
doc/src/sgml/func.sgml | 516 +++++++++-
src/backend/executor/execExpr.c | 206 +++-
src/backend/executor/execExprInterp.c | 543 ++++++++++
src/backend/jit/llvm/llvmjit_expr.c | 6 +
src/backend/jit/llvm/llvmjit_types.c | 1 +
src/backend/nodes/copyfuncs.c | 150 +++
src/backend/nodes/equalfuncs.c | 82 ++
src/backend/nodes/makefuncs.c | 15 +
src/backend/nodes/nodeFuncs.c | 186 +++-
src/backend/nodes/outfuncs.c | 70 ++
src/backend/nodes/readfuncs.c | 86 ++
src/backend/optimizer/path/costsize.c | 3 +-
src/backend/optimizer/util/clauses.c | 32 +
src/backend/parser/gram.y | 333 +++++-
src/backend/parser/parse_collate.c | 4 +
src/backend/parser/parse_expr.c | 490 ++++++++-
src/backend/parser/parse_target.c | 15 +
src/backend/utils/adt/formatting.c | 45 +-
src/backend/utils/adt/jsonb.c | 62 ++
src/backend/utils/adt/jsonfuncs.c | 50 +-
src/backend/utils/adt/jsonpath.c | 257 +++++
src/backend/utils/adt/jsonpath_exec.c | 350 ++++++-
src/backend/utils/adt/ruleutils.c | 135 +++
src/backend/utils/misc/queryjumble.c | 21 +
src/include/executor/execExpr.h | 54 +
src/include/executor/executor.h | 2 +
src/include/nodes/makefuncs.h | 1 +
src/include/nodes/nodes.h | 8 +
src/include/nodes/parsenodes.h | 59 ++
src/include/nodes/primnodes.h | 109 ++
src/include/parser/kwlist.h | 11 +
src/include/utils/formatting.h | 4 +
src/include/utils/jsonb.h | 3 +
src/include/utils/jsonfuncs.h | 4 +
src/include/utils/jsonpath.h | 33 +
src/test/regress/expected/json_sqljson.out | 15 +
src/test/regress/expected/jsonb_sqljson.out | 1018 +++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/json_sqljson.sql | 11 +
src/test/regress/sql/jsonb_sqljson.sql | 317 ++++++
40 files changed, 5185 insertions(+), 124 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/sql/json_sqljson.sql
create mode 100644 src/test/regress/sql/jsonb_sqljson.sql
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7a35a44075..decca95b1b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18394,6 +18394,21 @@ FROM films AS f;
<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>
@@ -18423,6 +18438,501 @@ INSERT INTO my_films VALUES (
</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>
@@ -18582,7 +19092,7 @@ FROM
<varlistentry>
<term>
<literal><replaceable>context_item</replaceable>, <replaceable>path_expression</replaceable>
-[ PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } [, ...]]</literal>
+<optional> PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } <optional>, ...</optional></optional></literal>
</term>
<listitem>
<para>
@@ -18611,7 +19121,7 @@ FROM
</listitem>
</itemizedlist>
<para>
- The input clause is common for all SQL/JSON query functions.
+ The input clause is common for all SQL/JSON query functions.
</para>
</listitem>
</varlistentry>
@@ -18625,7 +19135,7 @@ FROM
<variablelist>
<varlistentry>
<term>
- <literal>RETURNING <replaceable class="parameter">data_type</replaceable> [ FORMAT JSON [ ENCODING UTF8 ] ]</literal>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional></literal>
</term>
<listitem>
<para>
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 95a3787130..f6ec6fa846 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -47,6 +47,7 @@
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/datum.h"
+#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
@@ -85,6 +86,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 +157,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 +169,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);
}
/*
@@ -2526,6 +2524,112 @@ ExecInitExprRec(Expr *node, ExprState *state,
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->sval);
+ 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 438d482b1c..bd90b02be1 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -57,14 +57,18 @@
#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"
@@ -74,8 +78,10 @@
#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"
@@ -482,6 +488,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&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,
@@ -1805,7 +1812,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
{
/* 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();
}
@@ -4529,3 +4542,533 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
*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 02511c6aec..9c8f341d96 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2360,6 +2360,12 @@ llvm_compile_expr(ExprState *state)
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 4d7029a27f..b2bda86889 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -133,6 +133,7 @@ void *referenced_functions[] =
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 ce3102a452..e5c9f403e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2490,6 +2490,90 @@ _copyJsonArrayQueryConstructor(const JsonArrayQueryConstructor *from)
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
*/
@@ -2507,6 +2591,51 @@ _copyJsonIsPredicate(const JsonIsPredicate *from)
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
*
@@ -5598,6 +5727,27 @@ copyObjectImpl(const void *from)
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 0fda7680b3..8a60e66970 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -988,6 +988,76 @@ _equalJsonIsPredicate(const JsonIsPredicate *a,
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
*/
@@ -3520,6 +3590,18 @@ equal(const void *a, const void *b)
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
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index b67e7c5297..cd6c300e7b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -852,6 +852,21 @@ makeJsonValueExpr(Expr *expr, JsonFormat *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
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 191abcf32f..e8de1dd3aa 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -263,6 +263,12 @@ exprType(const Node *expr)
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 */
@@ -498,7 +504,11 @@ exprTypmod(const Node *expr)
case T_JsonValueExpr:
return exprTypmod((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
case T_JsonConstructorExpr:
- return -1; /* ((const JsonConstructorExpr *) expr)->returning->typmod; */
+ 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;
}
@@ -989,6 +999,21 @@ exprCollation(const Node *expr)
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 */
@@ -1218,6 +1243,21 @@ exprSetCollation(Node *expr, Oid collation)
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;
@@ -1673,6 +1713,15 @@ exprLocation(const Node *expr)
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;
@@ -2431,6 +2480,54 @@ expression_tree_walker(Node *node,
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));
@@ -3428,6 +3525,7 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
+ break;
case T_JsonIsPredicate:
{
JsonIsPredicate *pred = (JsonIsPredicate *) node;
@@ -3438,6 +3536,55 @@ expression_tree_mutator(Node *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));
@@ -4260,6 +4407,43 @@ raw_expression_tree_walker(Node *node,
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 ed5d159f6c..23750d97ab 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1807,6 +1807,64 @@ _outJsonIsPredicate(StringInfo str, const JsonIsPredicate *node)
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.
@@ -4608,6 +4666,18 @@ outNode(StringInfo str, const void *obj)
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 aaf947b6cb..c15e81a362 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1454,6 +1454,84 @@ _readJsonConstructorExpr(void)
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
*/
@@ -3065,6 +3143,14 @@ parseNodeString(void)
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 8dc7dd4ca2..c7b9d8d11a 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 bac86aad45..eef4381477 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"
@@ -52,6 +53,7 @@
#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"
@@ -405,6 +407,24 @@ contain_mutable_functions_walker(Node *node, void *context)
/* 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 */
@@ -876,6 +896,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,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1d589e9b41..ff680b61d8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -280,6 +280,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct GroupClause *groupclause;
struct KeyActions *keyactions;
struct KeyAction *keyaction;
+ JsonBehavior *jsbehavior;
+ struct
+ {
+ JsonBehavior *on_empty;
+ JsonBehavior *on_error;
+ } on_behavior;
+ JsonQuotes js_quotes;
}
%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -639,7 +646,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_representation
json_value_expr
json_func_expr
+ json_value_func_expr
+ json_query_expr
+ json_exists_predicate
+ 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
@@ -651,15 +665,43 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
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
@@ -699,7 +741,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
@@ -710,8 +752,8 @@ 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
@@ -726,7 +768,8 @@ 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 JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG
+ JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG
+ JSON_QUERY JSON_VALUE
KEY KEYS KEEP
@@ -741,7 +784,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
@@ -749,7 +792,7 @@ 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
@@ -759,7 +802,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
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 STRIP_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
@@ -767,7 +810,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
@@ -846,7 +889,8 @@ 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 ABSENT UNIQUE JSON
+%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 '+' '-'
@@ -15336,6 +15380,80 @@ opt_asymmetric: ASYMMETRIC
/* SQL/JSON support */
json_func_expr:
json_value_constructor
+ | json_value_func_expr
+ | json_query_expr
+ | json_exists_predicate
+ ;
+
+
+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:
@@ -15374,6 +15492,153 @@ 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 must 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
{
@@ -15386,6 +15651,35 @@ json_output_clause_opt:
| /* 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
@@ -16056,6 +16350,7 @@ unreserved_keyword:
| COMMIT
| COMMITTED
| COMPRESSION
+ | CONDITIONAL
| CONFIGURATION
| CONFLICT
| CONNECTION
@@ -16092,10 +16387,12 @@ unreserved_keyword:
| DOUBLE_P
| DROP
| EACH
+ | EMPTY_P
| ENABLE_P
| ENCODING
| ENCRYPTED
| ENUM_P
+ | ERROR_P
| ESCAPE
| EVENT
| EXCLUDE
@@ -16145,6 +16442,7 @@ unreserved_keyword:
| INVOKER
| ISOLATION
| JSON
+ | KEEP
| KEY
| KEYS
| LABEL
@@ -16189,6 +16487,7 @@ unreserved_keyword:
| OFF
| OIDS
| OLD
+ | OMIT
| OPERATOR
| OPTION
| OPTIONS
@@ -16218,6 +16517,7 @@ unreserved_keyword:
| PROGRAM
| PUBLICATION
| QUOTE
+ | QUOTES
| RANGE
| READ
| REASSIGN
@@ -16299,6 +16599,7 @@ unreserved_keyword:
| UESCAPE
| UNBOUNDED
| UNCOMMITTED
+ | UNCONDITIONAL
| UNENCRYPTED
| UNKNOWN
| UNLISTEN
@@ -16358,8 +16659,11 @@ col_name_keyword:
| INTERVAL
| JSON_ARRAY
| JSON_ARRAYAGG
+ | JSON_EXISTS
| JSON_OBJECT
| JSON_OBJECTAGG
+ | JSON_QUERY
+ | JSON_VALUE
| LEAST
| NATIONAL
| NCHAR
@@ -16427,6 +16731,7 @@ type_func_name_keyword:
| OVERLAPS
| RIGHT
| SIMILAR
+ | STRING
| TABLESAMPLE
| VERBOSE
;
@@ -16591,6 +16896,7 @@ bare_label_keyword:
| COMMITTED
| COMPRESSION
| CONCURRENTLY
+ | CONDITIONAL
| CONFIGURATION
| CONFLICT
| CONNECTION
@@ -16643,11 +16949,13 @@ bare_label_keyword:
| DROP
| EACH
| ELSE
+ | EMPTY_P
| ENABLE_P
| ENCODING
| ENCRYPTED
| END_P
| ENUM_P
+ | ERROR_P
| ESCAPE
| EVENT
| EXCLUDE
@@ -16716,8 +17024,11 @@ bare_label_keyword:
| JSON
| JSON_ARRAY
| JSON_ARRAYAGG
+ | JSON_EXISTS
| JSON_OBJECT
| JSON_OBJECTAGG
+ | JSON_QUERY
+ | JSON_VALUE
| KEEP
| KEY
| KEYS
@@ -16777,6 +17088,7 @@ bare_label_keyword:
| OFF
| OIDS
| OLD
+ | OMIT
| ONLY
| OPERATOR
| OPTION
@@ -16813,6 +17125,7 @@ bare_label_keyword:
| PROGRAM
| PUBLICATION
| QUOTE
+ | QUOTES
| RANGE
| READ
| REAL
@@ -16881,6 +17194,7 @@ bare_label_keyword:
| STORAGE
| STORED
| STRICT_P
+ | STRING
| STRIP_P
| SUBSCRIPTION
| SUBSTRING
@@ -16914,6 +17228,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 6c793b72ec..2e549e7b39 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 0e504096fd..7709a6c665 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -86,6 +86,8 @@ static Node *transformJsonArrayQueryConstructor(ParseState *pstate,
static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg);
static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg);
static Node *transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *p);
+static Node *transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *p);
+static Node *transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve);
static Node *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -337,6 +339,14 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr);
break;
+ case T_JsonFuncExpr:
+ result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr);
+ break;
+
+ case T_JsonValueExpr:
+ result = transformJsonValueExpr(pstate, (JsonValueExpr *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3214,8 +3224,8 @@ makeCaseTestExpr(Node *expr)
* default format otherwise.
*/
static Node *
-transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
- JsonFormatType default_format)
+transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
+ JsonFormatType default_format, bool isarg)
{
Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr);
Node *rawexpr;
@@ -3234,6 +3244,8 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
get_type_category_preferred(exprtype, &typcategory, &typispreferred);
+ rawexpr = expr;
+
if (ve->format->format_type != JS_FORMAT_DEFAULT)
{
if (ve->format->encoding != JS_ENC_DEFAULT && exprtype != BYTEAOID)
@@ -3252,12 +3264,44 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
else
format = ve->format->format_type;
}
+ 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)
+ if (format == JS_FORMAT_DEFAULT)
+ expr = rawexpr;
+ else
{
Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
Node *orig = makeCaseTestExpr(expr);
@@ -3265,7 +3309,7 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
expr = orig;
- if (exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+ if (!isarg && exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg(ve->format->format_type == JS_FORMAT_DEFAULT ?
@@ -3316,6 +3360,24 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *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);
+}
+
+/*
+ * Transform JSON value expression using unspecified format by default.
+ */
+static Node *
+transformJsonValueExprDefault(ParseState *pstate, JsonValueExpr *jve)
+{
+ return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false);
+}
+
/*
* Checks specified output format for its applicability to the target type.
*/
@@ -3576,8 +3638,7 @@ transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor)
{
JsonKeyValue *kv = castNode(JsonKeyValue, lfirst(lc));
Node *key = transformExprRecurse(pstate, (Node *) kv->key);
- Node *val = transformJsonValueExpr(pstate, kv->value,
- JS_FORMAT_DEFAULT);
+ Node *val = transformJsonValueExprDefault(pstate, kv->value);
args = lappend(args, key);
args = lappend(args, val);
@@ -3755,7 +3816,7 @@ transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg)
Oid aggtype;
key = transformExprRecurse(pstate, (Node *) agg->arg->key);
- val = transformJsonValueExpr(pstate, agg->arg->value, JS_FORMAT_DEFAULT);
+ val = transformJsonValueExprDefault(pstate, agg->arg->value);
args = list_make2(key, val);
returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
@@ -3813,7 +3874,7 @@ transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg)
const char *aggfnname;
Oid aggtype;
- arg = transformJsonValueExpr(pstate, agg->arg, JS_FORMAT_DEFAULT);
+ arg = transformJsonValueExprDefault(pstate, agg->arg);
returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
list_make1(arg));
@@ -3861,8 +3922,7 @@ transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor)
foreach(lc, ctor->exprs)
{
JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc));
- Node *val = transformJsonValueExpr(pstate, jsval,
- JS_FORMAT_DEFAULT);
+ Node *val = transformJsonValueExprDefault(pstate, jsval);
args = lappend(args, val);
}
@@ -3945,3 +4005,413 @@ transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred)
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);
+
+ 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_type == JS_FORMAT_DEFAULT)
+ is_jsonb = exprType(context_item) == JSONBOID;
+ else
+ is_jsonb = ret->format->format_type == 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_type = 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_type = 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;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 204d285773..ef1eda6532 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1970,6 +1970,21 @@ FigureColnameInternal(Node *node, char **name)
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/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index d4c2e7b069..fb395b6f78 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -1017,11 +1017,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
* ----------
@@ -6666,3 +6661,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/jsonb.c b/src/backend/utils/adt/jsonb.c
index a103cbc7c6..d383cbdfed 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2227,3 +2227,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/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 03e7f10cd2..2023bcd125 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2777,11 +2777,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);
@@ -3253,6 +3253,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)
{
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 9be4e305ff..ca1cfe3d36 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->sval, 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 eff3734b6a..7811fa31e0 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 be8a75d8a7..fb52f77334 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -496,6 +496,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 ")
@@ -8099,6 +8101,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_WindowFunc:
case T_FuncExpr:
case T_JsonConstructorExpr:
+ case T_JsonExpr:
/* function-like: name(..) or name[..] */
return true;
@@ -8216,6 +8219,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;
@@ -8381,6 +8385,19 @@ get_rule_expr_paren(Node *node, deparse_context *context,
appendStringInfoChar(context->buf, ')');
}
+
+/*
+ * 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
*/
@@ -8424,6 +8441,66 @@ get_json_returning(JsonReturning *returning, StringInfo buf,
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
*
@@ -9583,6 +9660,7 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+
case T_JsonValueExpr:
{
JsonValueExpr *jve = (JsonValueExpr *) node;
@@ -9630,6 +9708,62 @@ get_rule_expr(Node *node, deparse_context *context,
}
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))->sval);
+ }
+ }
+
+ 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;
@@ -9753,6 +9887,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:
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index 8315812793..7120836c70 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -785,6 +785,27 @@ JumbleExpr(JumbleState *jstate, Node *node)
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)->sval);
+ }
+ 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/executor/execExpr.h b/src/include/executor/execExpr.h
index a41722ae1e..240d07982a 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 */
@@ -241,6 +242,7 @@ typedef enum ExprEvalOp
EEOP_SUBPLAN,
EEOP_JSON_CONSTRUCTOR,
EEOP_IS_JSON,
+ EEOP_JSONEXPR,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -687,6 +689,50 @@ typedef struct ExprEvalStep
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;
@@ -791,6 +837,14 @@ 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 344399f6a8..538d7eca07 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 380940968b..872f2f0828 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -109,6 +109,7 @@ extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_
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,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 666b45c5da..e5fa0fe4d9 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -205,6 +205,9 @@ typedef enum NodeTag
T_JsonReturning,
T_JsonValueExpr,
T_JsonConstructorExpr,
+ T_JsonExpr,
+ T_JsonCoercion,
+ T_JsonItemCoercions,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -501,8 +504,13 @@ typedef enum NodeTag
T_JsonAggConstructor,
T_JsonObjectAgg,
T_JsonArrayAgg,
+ T_JsonFuncExpr,
T_JsonIsPredicate,
+ T_JsonExistsPredicate,
+ T_JsonCommon,
+ T_JsonArgument,
T_JsonKeyValue,
+ T_JsonBehavior,
T_JsonOutput,
/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 40fb034028..157909b978 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1553,6 +1553,23 @@ typedef struct 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])
@@ -1564,6 +1581,48 @@ typedef struct JsonOutput
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
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3c4c248e29..97a00eb2db 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1235,6 +1235,17 @@ 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
@@ -1258,6 +1269,37 @@ typedef enum JsonFormatType
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
@@ -1345,6 +1387,73 @@ typedef struct JsonIsPredicate
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 1726d73da6..69590905c1 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -93,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)
@@ -147,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)
@@ -232,8 +235,12 @@ PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_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_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)
@@ -297,6 +304,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)
@@ -338,6 +346,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)
@@ -408,6 +417,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)
@@ -442,6 +452,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 851e787bfd..0a22af80a2 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/jsonb.h b/src/include/utils/jsonb.h
index 6bcf35dd0a..3fdff445cf 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -407,6 +407,9 @@ 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);
diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h
index cd16b6c0c8..62dc3d88a4 100644
--- a/src/include/utils/jsonfuncs.h
+++ b/src/include/utils/jsonfuncs.h
@@ -58,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 cd0b5d5b61..98a61d7f72 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/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_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
new file mode 100644
index 0000000000..1126d7caf5
--- /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 must 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 must 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 must 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 must 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/parallel_schedule b/src/test/regress/parallel_schedule
index 3ce701a588..b8cea3a5f2 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,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 sqljson
+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_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;
--
2.25.1
[text/x-patch] 0005-SQL-JSON-functions-for-json-type-v64.patch (57.0K, ../../[email protected]/6-0005-SQL-JSON-functions-for-json-type-v64.patch)
download | inline diff:
From 1c11ff98f52a0a84821c3ff66865357edc21d0cc Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:15:13 -0500
Subject: [PATCH 5/6] SQL JSON functions for json type
---
doc/src/sgml/func.sgml | 325 +++++++++++++++++-
doc/src/sgml/keywords/sql2016-02-reserved.txt | 3 +
src/backend/executor/execExpr.c | 45 +++
src/backend/executor/execExprInterp.c | 42 ++-
src/backend/nodes/copyfuncs.c | 35 ++
src/backend/nodes/equalfuncs.c | 25 ++
src/backend/parser/gram.y | 56 ++-
src/backend/parser/parse_expr.c | 152 +++++++-
src/backend/parser/parse_target.c | 9 +
src/backend/utils/adt/format_type.c | 4 +
src/backend/utils/adt/json.c | 51 ++-
src/backend/utils/adt/jsonb.c | 64 ++--
src/backend/utils/adt/ruleutils.c | 13 +-
src/include/executor/execExpr.h | 5 +
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 35 ++
src/include/nodes/primnodes.h | 5 +-
src/include/parser/kwlist.h | 4 +-
src/include/utils/json.h | 21 +-
src/include/utils/jsonb.h | 21 ++
src/test/regress/expected/sqljson.out | 267 ++++++++++++++
src/test/regress/sql/sqljson.sql | 57 +++
22 files changed, 1158 insertions(+), 84 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index decca95b1b..e19821c4b2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17621,11 +17621,21 @@ $.* ? (@ like_regex "^\\d+$")
<para>
<productname>PostgreSQL</productname> provides several functions
that generate JSON data. Taking values of SQL types as input, these
- functions construct JSON objects or JSON arrays represented as
- SQL character or binary strings.
+ 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>
@@ -17648,6 +17658,219 @@ $.* ? (@ like_regex "^\\d+$")
</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>
@@ -19080,6 +19303,104 @@ FROM
</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">
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 f6ec6fa846..25253f5377 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -47,6 +47,8 @@
#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"
@@ -2460,6 +2462,12 @@ ExecInitExprRec(Expr *node, ExprState *state,
{
ExecInitExprRec(ctor->func, state, resv, resnull);
}
+ else if ((ctor->type == JSCTOR_JSON_PARSE && !ctor->unique) ||
+ ctor->type == JSCTOR_JSON_SERIALIZE)
+ {
+ /* Use the value of the first argument as a result */
+ ExecInitExprRec(linitial(args), state, resv, resnull);
+ }
else
{
scratch.opcode = EEOP_JSON_CONSTRUCTOR;
@@ -2492,6 +2500,43 @@ ExecInitExprRec(Expr *node, ExprState *state,
argno++;
}
+ /* prepare type cache for datum_to_json[b]() */
+ if (ctor->type == JSCTOR_JSON_SCALAR)
+ {
+ bool is_jsonb =
+ ctor->returning->format->format_type == JS_FORMAT_JSONB;
+
+ 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);
}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index bd90b02be1..0483279f91 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3982,7 +3982,7 @@ ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op)
* JSON text validation.
*/
if (res && (pred->unique_keys || exprtype == TEXTOID))
- res = json_validate(json, pred->unique_keys);
+ res = json_validate(json, pred->unique_keys, false);
}
else if (exprtype == JSONBOID)
{
@@ -4533,6 +4533,46 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
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;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e5c9f403e6..c3242af8f2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2344,6 +2344,35 @@ _copyJsonValueExpr(const JsonValueExpr *from)
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
*/
@@ -5697,6 +5726,12 @@ copyObjectImpl(const void *from)
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;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a60e66970..731b1419f6 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -871,6 +871,25 @@ _equalJsonValueExpr(const JsonValueExpr *a, const JsonValueExpr *b)
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)
{
@@ -3584,6 +3603,12 @@ equal(const void *a, const void *b)
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;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff680b61d8..175e14e51d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -562,7 +562,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
@@ -649,6 +649,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
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
@@ -769,7 +772,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG
- JSON_QUERY JSON_VALUE
+ JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_VALUE
KEY KEYS KEEP
@@ -13132,6 +13135,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
@@ -13150,6 +13154,7 @@ ConstTypename:
| ConstBit { $$ = $1; }
| ConstCharacter { $$ = $1; }
| ConstDatetime { $$ = $1; }
+ | JsonType { $$ = $1; }
;
/*
@@ -13518,6 +13523,13 @@ interval_second:
}
;
+JsonType:
+ JSON
+ {
+ $$ = SystemTypeName("json");
+ $$->location = @1;
+ }
+ ;
/*****************************************************************************
*
@@ -15383,8 +15395,42 @@ json_func_expr:
| 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 ')'
+ {
+ JsonParseExpr *n = makeNode(JsonParseExpr);
+ n->expr = (JsonValueExpr *) $3;
+ n->unique_keys = $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_scalar_expr:
+ JSON_SCALAR '(' a_expr ')'
+ {
+ JsonScalarExpr *n = makeNode(JsonScalarExpr);
+ n->expr = (Expr *) $3;
+ 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 '('
@@ -16441,7 +16487,6 @@ unreserved_keyword:
| INSTEAD
| INVOKER
| ISOLATION
- | JSON
| KEEP
| KEY
| KEYS
@@ -16657,12 +16702,15 @@ 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
@@ -17028,6 +17076,8 @@ bare_label_keyword:
| JSON_OBJECT
| JSON_OBJECTAGG
| JSON_QUERY
+ | JSON_SCALAR
+ | JSON_SERIALIZE
| JSON_VALUE
| KEEP
| KEY
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 7709a6c665..bbd80e5c6e 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -88,6 +88,10 @@ 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,
@@ -347,6 +351,18 @@ transformExprRecurse(ParseState *pstate, Node *expr)
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));
@@ -3225,7 +3241,8 @@ makeCaseTestExpr(Node *expr)
*/
static Node *
transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
- JsonFormatType default_format, bool isarg)
+ JsonFormatType default_format, bool isarg,
+ Oid targettype)
{
Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr);
Node *rawexpr;
@@ -3299,17 +3316,17 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
else
format = default_format;
- if (format == JS_FORMAT_DEFAULT)
+ if (format == JS_FORMAT_DEFAULT &&
+ (!OidIsValid(targettype) || exprtype == targettype))
expr = rawexpr;
else
{
- Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
Node *orig = makeCaseTestExpr(expr);
Node *coerced;
+ bool cast_is_needed = OidIsValid(targettype);
- expr = orig;
-
- if (!isarg && exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+ if (!isarg && !cast_is_needed &&
+ exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg(ve->format->format_type == JS_FORMAT_DEFAULT ?
@@ -3318,6 +3335,8 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
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)
{
@@ -3325,6 +3344,9 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
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,
@@ -3335,11 +3357,21 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
if (!coerced)
{
/* If coercion failed, use to_json()/to_jsonb() functions. */
- Oid fnoid = targettype == JSONOID ? F_TO_JSON : F_TO_JSONB;
- FuncExpr *fexpr = makeFuncExpr(fnoid, targettype,
- list_make1(expr),
- InvalidOid, InvalidOid,
- COERCE_EXPLICIT_CALL);
+ FuncExpr *fexpr;
+ Oid fnoid;
+
+ 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;
@@ -3366,7 +3398,8 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
static Node *
transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve)
{
- return transformJsonValueExprExt(pstate, jve, JS_FORMAT_JSON, false);
+ return transformJsonValueExprExt(pstate, jve, JS_FORMAT_JSON, false,
+ InvalidOid);
}
/*
@@ -3375,7 +3408,8 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve)
static Node *
transformJsonValueExprDefault(ParseState *pstate, JsonValueExpr *jve)
{
- return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false);
+ return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false,
+ InvalidOid);
}
/*
@@ -4022,7 +4056,7 @@ transformJsonPassingArgs(ParseState *pstate, JsonFormatType format, List *args,
{
JsonArgument *arg = castNode(JsonArgument, lfirst(lc));
Node *expr = transformJsonValueExprExt(pstate, arg->val,
- format, true);
+ format, true, InvalidOid);
assign_expr_collations(pstate, expr);
@@ -4415,3 +4449,93 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
return (Node *) jsexpr;
}
+
+/*
+ * Transform a JSON() expression.
+ */
+static Node *
+transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr)
+{
+ JsonReturning *returning = makeNode(JsonReturning);
+ Node *arg;
+
+ returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1);
+ returning->typid = JSONOID;
+ returning->typmod = -1;
+
+ 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)
+{
+ JsonReturning *returning = makeNode(JsonReturning);
+ Node *arg = transformExprRecurse(pstate, (Node *) jsexpr->expr);
+
+ returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1);
+ returning->typid = JSONOID;
+ returning->typmod = -1;
+
+ 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 ef1eda6532..829c0f9497 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1957,6 +1957,15 @@ 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;
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 2918fdbfb6..060fd7e183 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -294,6 +294,10 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
else
buf = pstrdup("character varying");
break;
+
+ case JSONOID:
+ buf = pstrdup("json");
+ break;
}
if (buf == NULL)
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 5edcb8bb60..492796eb83 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -30,21 +30,6 @@
#include "utils/lsyscache.h"
#include "utils/typcache.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;
-
/* Common context for key uniqueness check */
typedef struct HTAB *JsonUniqueCheckState; /* hash table for key names */
@@ -99,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);
@@ -180,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)
@@ -762,6 +744,16 @@ 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)
{
@@ -802,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;
@@ -814,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));
}
/*
@@ -1712,7 +1699,7 @@ json_unique_object_field_start(void *_state, char *field, bool isnull)
/* Validate JSON text and additionally check key uniqueness */
bool
-json_validate(text *json, bool check_unique_keys)
+json_validate(text *json, bool check_unique_keys, bool throw_error)
{
JsonLexContext *lex = makeJsonLexContext(json, check_unique_keys);
JsonSemAction uniqueSemAction = {0};
@@ -1736,10 +1723,22 @@ json_validate(text *json, bool check_unique_keys)
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 */
}
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index d383cbdfed..2043f2e74a 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -34,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;
@@ -62,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);
@@ -71,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);
@@ -99,7 +77,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);
}
/*
@@ -123,7 +101,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);
}
/*
@@ -164,6 +142,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.
*/
@@ -254,7 +240,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;
@@ -264,6 +250,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;
@@ -298,6 +286,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
@@ -620,7 +609,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)
@@ -1127,6 +1116,18 @@ 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)
{
@@ -1168,7 +1169,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;
@@ -1180,11 +1180,7 @@ 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));
}
Datum
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fb52f77334..c4cac590af 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10052,7 +10052,9 @@ get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf)
if (ctor->unique)
appendStringInfoString(buf, " WITH UNIQUE KEYS");
- get_json_returning(ctor->returning, buf, true);
+ if (ctor->type != JSCTOR_JSON_PARSE &&
+ ctor->type != JSCTOR_JSON_SCALAR)
+ get_json_returning(ctor->returning, buf, true);
}
static void
@@ -10066,6 +10068,15 @@ get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context,
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;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 240d07982a..9ce8df17e5 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -680,6 +680,11 @@ typedef struct ExprEvalStep
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;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e5fa0fe4d9..90c1451e1c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -204,6 +204,9 @@ typedef enum NodeTag
T_JsonFormat,
T_JsonReturning,
T_JsonValueExpr,
+ T_JsonParseExpr,
+ T_JsonScalarExpr,
+ T_JsonSerializeExpr,
T_JsonConstructorExpr,
T_JsonExpr,
T_JsonCoercion,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 157909b978..13a18e02a2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1635,6 +1635,41 @@ typedef struct JsonKeyValue
JsonValueExpr *value; /* JSON value expression */
} JsonKeyValue;
+/*
+ * JsonParseExpr -
+ * untransformed representation of JSON()
+ */
+typedef struct JsonParseExpr
+{
+ NodeTag type;
+ JsonValueExpr *expr; /* string expression */
+ 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 */
+ 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
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 97a00eb2db..90108befeb 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1341,7 +1341,10 @@ typedef enum JsonConstructorType
JSCTOR_JSON_OBJECT = 1,
JSCTOR_JSON_ARRAY = 2,
JSCTOR_JSON_OBJECTAGG = 3,
- JSCTOR_JSON_ARRAYAGG = 4
+ JSCTOR_JSON_ARRAYAGG = 4,
+ JSCTOR_JSON_SCALAR = 5,
+ JSCTOR_JSON_SERIALIZE = 6,
+ JSCTOR_JSON_PARSE = 7
} JsonConstructorType;
/*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 69590905c1..ab3d8e0b52 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -232,13 +232,15 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL)
PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
-PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json", JSON, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_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)
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index bfe5b21591..da4a9257b3 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -16,16 +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);
+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 3fdff445cf..bae466b523 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -376,6 +376,22 @@ 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;
/* Support functions */
extern uint32 getJsonbOffset(const JsonbContainer *jc, int index);
@@ -403,6 +419,7 @@ 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,
@@ -418,6 +435,10 @@ extern Datum jsonb_set_element(Jsonb *jb, Datum *path, int path_len,
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);
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 27dca7815a..11f5eb2d2c 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -1,3 +1,270 @@
+-- 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)
+
+-- JSON_SCALAR()
+SELECT JSON_SCALAR();
+ERROR: syntax error at or near ")"
+LINE 1: SELECT JSON_SCALAR();
+ ^
+SELECT JSON_SCALAR(NULL);
+ json_scalar
+-------------
+
+(1 row)
+
+SELECT JSON_SCALAR(NULL::int);
+ json_scalar
+-------------
+
+(1 row)
+
+SELECT JSON_SCALAR(123);
+ json_scalar
+-------------
+ 123
+(1 row)
+
+SELECT JSON_SCALAR(123.45);
+ json_scalar
+-------------
+ 123.45
+(1 row)
+
+SELECT JSON_SCALAR(123.45::numeric);
+ json_scalar
+-------------
+ 123.45
+(1 row)
+
+SELECT JSON_SCALAR(true);
+ json_scalar
+-------------
+ true
+(1 row)
+
+SELECT JSON_SCALAR(false);
+ json_scalar
+-------------
+ false
+(1 row)
+
+SELECT JSON_SCALAR(' 123.45');
+ json_scalar
+-------------
+ " 123.45"
+(1 row)
+
+SELECT JSON_SCALAR('2020-06-07'::date);
+ json_scalar
+--------------
+ "2020-06-07"
+(1 row)
+
+SELECT JSON_SCALAR('2020-06-07 01:02:03'::timestamp);
+ json_scalar
+-----------------------
+ "2020-06-07T01:02:03"
+(1 row)
+
+SELECT JSON_SCALAR('{}'::json);
+ json_scalar
+-------------
+ {}
+(1 row)
+
+SELECT JSON_SCALAR('{}'::jsonb);
+ json_scalar
+-------------
+ {}
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123);
+ QUERY PLAN
+----------------------------
+ Result
+ Output: JSON_SCALAR(123)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123');
+ QUERY PLAN
+------------------------------------
+ Result
+ Output: JSON_SCALAR('123'::text)
+(2 rows)
+
+-- JSON_SERIALIZE()
+SELECT JSON_SERIALIZE();
+ERROR: syntax error at or near ")"
+LINE 1: SELECT JSON_SERIALIZE();
+ ^
+SELECT JSON_SERIALIZE(NULL);
+ json_serialize
+----------------
+
+(1 row)
+
+SELECT JSON_SERIALIZE(JSON('{ "a" : 1 } '));
+ json_serialize
+----------------
+ { "a" : 1 }
+(1 row)
+
+SELECT JSON_SERIALIZE('{ "a" : 1 } ');
+ json_serialize
+----------------
+ { "a" : 1 }
+(1 row)
+
+SELECT JSON_SERIALIZE('1');
+ json_serialize
+----------------
+ 1
+(1 row)
+
+SELECT JSON_SERIALIZE('1' FORMAT JSON);
+ json_serialize
+----------------
+ 1
+(1 row)
+
+SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING bytea);
+ json_serialize
+----------------------------
+ \x7b20226122203a2031207d20
+(1 row)
+
+SELECT 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
diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
index 4f3c06dcb3..98bd93c110 100644
--- a/src/test/regress/sql/sqljson.sql
+++ b/src/test/regress/sql/sqljson.sql
@@ -1,3 +1,60 @@
+-- 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);
+
+
+-- JSON_SCALAR()
+SELECT JSON_SCALAR();
+SELECT JSON_SCALAR(NULL);
+SELECT JSON_SCALAR(NULL::int);
+SELECT JSON_SCALAR(123);
+SELECT JSON_SCALAR(123.45);
+SELECT JSON_SCALAR(123.45::numeric);
+SELECT JSON_SCALAR(true);
+SELECT JSON_SCALAR(false);
+SELECT JSON_SCALAR(' 123.45');
+SELECT JSON_SCALAR('2020-06-07'::date);
+SELECT JSON_SCALAR('2020-06-07 01:02:03'::timestamp);
+SELECT JSON_SCALAR('{}'::json);
+SELECT JSON_SCALAR('{}'::jsonb);
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123');
+
+-- JSON_SERIALIZE()
+SELECT JSON_SERIALIZE();
+SELECT JSON_SERIALIZE(NULL);
+SELECT JSON_SERIALIZE(JSON('{ "a" : 1 } '));
+SELECT JSON_SERIALIZE('{ "a" : 1 } ');
+SELECT JSON_SERIALIZE('1');
+SELECT JSON_SERIALIZE('1' FORMAT JSON);
+SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING bytea);
+SELECT 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);
--
2.25.1
[text/x-patch] 0006-RETURNING-clause-for-JSON-and-JSON_SCALAR-v64.patch (8.2K, ../../[email protected]/7-0006-RETURNING-clause-for-JSON-and-JSON_SCALAR-v64.patch)
download | inline diff:
From 31ead5d65852012876af2194f2ff8daf6fceeaf9 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Sat, 5 Mar 2022 08:07:15 -0500
Subject: [PATCH 6/6] RETURNING clause for JSON() and JSON_SCALAR()
---
src/backend/parser/gram.y | 7 +++-
src/backend/parser/parse_expr.c | 46 ++++++++++++++++-----
src/backend/utils/adt/ruleutils.c | 5 ++-
src/include/nodes/parsenodes.h | 2 +
src/test/regress/expected/sqljson.out | 57 +++++++++++++++++++++++++++
src/test/regress/sql/sqljson.sql | 10 +++++
6 files changed, 113 insertions(+), 14 deletions(-)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 175e14e51d..70f541ece0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -15401,21 +15401,24 @@ json_func_expr:
;
json_parse_expr:
- JSON '(' json_value_expr json_key_uniqueness_constraint_opt ')'
+ JSON '(' json_value_expr json_key_uniqueness_constraint_opt
+ json_returning_clause_opt ')'
{
JsonParseExpr *n = makeNode(JsonParseExpr);
n->expr = (JsonValueExpr *) $3;
n->unique_keys = $4;
+ n->output = (JsonOutput *) $5;
n->location = @1;
$$ = (Node *) n;
}
;
json_scalar_expr:
- JSON_SCALAR '(' a_expr ')'
+ JSON_SCALAR '(' a_expr json_returning_clause_opt ')'
{
JsonScalarExpr *n = makeNode(JsonScalarExpr);
n->expr = (Expr *) $3;
+ n->output = (JsonOutput *) $4;
n->location = @1;
$$ = (Node *) n;
}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index bbd80e5c6e..18622fd013 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4450,19 +4450,48 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
return (Node *) jsexpr;
}
+static JsonReturning *
+transformJsonConstructorRet(ParseState *pstate, JsonOutput *output, const char *fname)
+{
+ JsonReturning *returning;
+
+ if (output)
+ {
+ returning = transformJsonOutput(pstate, output, false);
+
+ Assert(OidIsValid(returning->typid));
+
+ if (returning->typid != JSONOID && returning->typid != JSONBOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot use RETURNING type %s in %s",
+ format_type_be(returning->typid), fname),
+ parser_errposition(pstate, output->typeName->location)));
+ }
+ else
+ {
+ Oid targettype = JSONOID;
+ JsonFormatType format = JS_FORMAT_JSON;
+
+ returning = makeNode(JsonReturning);
+ returning->format = makeJsonFormat(format, JS_ENC_DEFAULT, -1);
+ returning->typid = targettype;
+ returning->typmod = -1;
+ }
+
+ return returning;
+}
+
/*
* Transform a JSON() expression.
*/
static Node *
transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr)
{
- JsonReturning *returning = makeNode(JsonReturning);
+ JsonReturning *returning = transformJsonConstructorRet(pstate, jsexpr->output,
+ "JSON()");
Node *arg;
- returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1);
- returning->typid = JSONOID;
- returning->typmod = -1;
-
if (jsexpr->unique_keys)
{
/*
@@ -4502,12 +4531,9 @@ transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr)
static Node *
transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *jsexpr)
{
- JsonReturning *returning = makeNode(JsonReturning);
Node *arg = transformExprRecurse(pstate, (Node *) jsexpr->expr);
-
- returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1);
- returning->typid = JSONOID;
- returning->typmod = -1;
+ JsonReturning *returning = transformJsonConstructorRet(pstate, jsexpr->output,
+ "JSON_SCALAR()");
if (exprType(arg) == UNKNOWNOID)
arg = coerce_to_specific_type(pstate, arg, TEXTOID, "JSON_SCALAR");
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c4cac590af..bc5b545fd1 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10052,8 +10052,9 @@ get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf)
if (ctor->unique)
appendStringInfoString(buf, " WITH UNIQUE KEYS");
- if (ctor->type != JSCTOR_JSON_PARSE &&
- ctor->type != JSCTOR_JSON_SCALAR)
+ if (!((ctor->type == JSCTOR_JSON_PARSE ||
+ ctor->type == JSCTOR_JSON_SCALAR) &&
+ ctor->returning->typid == JSONOID))
get_json_returning(ctor->returning, buf, true);
}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 13a18e02a2..cf19a225db 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1643,6 +1643,7 @@ typedef struct JsonParseExpr
{
NodeTag type;
JsonValueExpr *expr; /* string expression */
+ JsonOutput *output; /* RETURNING clause, if specified */
bool unique_keys; /* WITH UNIQUE KEYS? */
int location; /* token location, or -1 if unknown */
} JsonParseExpr;
@@ -1655,6 +1656,7 @@ typedef struct JsonScalarExpr
{
NodeTag type;
Expr *expr; /* scalar expression */
+ JsonOutput *output; /* RETURNING clause, if specified */
int location; /* token location, or -1 if unknown */
} JsonScalarExpr;
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 11f5eb2d2c..6cadd87868 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -113,6 +113,49 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITHOUT UNIQUE KEYS);
Output: JSON('123'::json)
(2 rows)
+SELECT JSON('123' RETURNING text);
+ERROR: cannot use RETURNING type text in JSON()
+LINE 1: SELECT JSON('123' RETURNING text);
+ ^
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123');
+ QUERY PLAN
+-----------------------------
+ Result
+ Output: JSON('123'::json)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json);
+ QUERY PLAN
+-----------------------------
+ Result
+ Output: JSON('123'::json)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb);
+ QUERY PLAN
+----------------------------------------------
+ Result
+ Output: JSON('123'::jsonb RETURNING jsonb)
+(2 rows)
+
+SELECT pg_typeof(JSON('123'));
+ pg_typeof
+-----------
+ json
+(1 row)
+
+SELECT pg_typeof(JSON('123' RETURNING json));
+ pg_typeof
+-----------
+ json
+(1 row)
+
+SELECT pg_typeof(JSON('123' RETURNING jsonb));
+ pg_typeof
+-----------
+ jsonb
+(1 row)
+
-- JSON_SCALAR()
SELECT JSON_SCALAR();
ERROR: syntax error at or near ")"
@@ -204,6 +247,20 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123');
Output: JSON_SCALAR('123'::text)
(2 rows)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json);
+ QUERY PLAN
+----------------------------
+ Result
+ Output: JSON_SCALAR(123)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb);
+ QUERY PLAN
+--------------------------------------------
+ Result
+ Output: JSON_SCALAR(123 RETURNING jsonb)
+(2 rows)
+
-- JSON_SERIALIZE()
SELECT JSON_SERIALIZE();
ERROR: syntax error at or near ")"
diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
index 98bd93c110..51fc659b58 100644
--- a/src/test/regress/sql/sqljson.sql
+++ b/src/test/regress/sql/sqljson.sql
@@ -23,6 +23,14 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON ENCODING UTF8)
EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITH UNIQUE KEYS);
EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITHOUT UNIQUE KEYS);
+SELECT JSON('123' RETURNING text);
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123');
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb);
+SELECT pg_typeof(JSON('123'));
+SELECT pg_typeof(JSON('123' RETURNING json));
+SELECT pg_typeof(JSON('123' RETURNING jsonb));
-- JSON_SCALAR()
SELECT JSON_SCALAR();
@@ -41,6 +49,8 @@ SELECT JSON_SCALAR('{}'::jsonb);
EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123);
EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123');
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb);
-- JSON_SERIALIZE()
SELECT JSON_SERIALIZE();
--
2.25.1
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-22 22:16 Andrew Dunstan <[email protected]>
parent: Andrew Dunstan <[email protected]>
1 sibling, 1 reply; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-22 22:16 UTC (permalink / raw)
To: Himanshu Upadhyaya <[email protected]>; +Cc: Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 3/5/22 09:39, Andrew Dunstan wrote:
>
> here's a new set of patches, omitting the GUC patch and with the
> beginnings of some message cleanup - there's more work to do there.
>
>
>
> This patchset restores the RETURNING clause for JSON() and JSON_SCALAR()
> but without the GUC
>
>
I have committed the first of these. That will break the cfbot for this
and the json_table patches. The remainder should be committed in the
following days.
cheers
andew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-22 22:20 Tom Lane <[email protected]>
parent: Andrew Dunstan <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Tom Lane @ 2022-03-22 22:20 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
Andrew Dunstan <[email protected]> writes:
> I have committed the first of these. That will break the cfbot for this
> and the json_table patches. The remainder should be committed in the
> following days.
That patch is 0-for-three on my buildfarm animals.
regards, tom lane
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-22 22:31 Tom Lane <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 2 replies; 95+ messages in thread
From: Tom Lane @ 2022-03-22 22:31 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
I wrote:
> That patch is 0-for-three on my buildfarm animals.
... the reason being that they use -Werror, and this patch spews
a bunch of warnings. This is not acceptable, especially not in
the middle of a CF when other people are trying to get work done.
Please revert.
regards, tom lane
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-22 23:33 Andres Freund <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 1 reply; 95+ messages in thread
From: Andres Freund @ 2022-03-22 23:33 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
Hi,
On 2022-03-22 18:31:39 -0400, Tom Lane wrote:
> I wrote:
> > That patch is 0-for-three on my buildfarm animals.
>
> ... the reason being that they use -Werror, and this patch spews
> a bunch of warnings. This is not acceptable, especially not in
> the middle of a CF when other people are trying to get work done.
> Please revert.
There's also https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=jabiru&dt=2022-03-22%2022%3A25%3A26
that started failing with
../../preproc/ecpg --regression -I./../../include -I. -o test1.c test1.pgc
test1.pgc:12: ERROR: syntax error at or near "int"
with this commit.
And the bison warnings I complained about on -committers:
https://www.postgresql.org/message-id/20220322223319.so4ajcki7wwaujin%40alap3.anarazel.de
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-22 23:48 Tom Lane <[email protected]>
parent: Andres Freund <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Tom Lane @ 2022-03-22 23:48 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
Andres Freund <[email protected]> writes:
> There's also
> https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=jabiru&dt=2022-03-22%2022%3A25%3A26
> that started failing with
> ../../preproc/ecpg --regression -I./../../include -I. -o test1.c test1.pgc
> test1.pgc:12: ERROR: syntax error at or near "int"
> with this commit.
Yeah, I was just scratching my head about that. It's not clear how
that could be related; but jabiru doesn't seem to have a history of
random failures, so maybe it's real.
regards, tom lane
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-22 23:56 Andrew Dunstan <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 0 replies; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-22 23:56 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 3/22/22 18:31, Tom Lane wrote:
> I wrote:
>> That patch is 0-for-three on my buildfarm animals.
> ... the reason being that they use -Werror, and this patch spews
> a bunch of warnings. This is not acceptable, especially not in
> the middle of a CF when other people are trying to get work done.
> Please revert.
>
>
reverted.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-23 01:07 Tom Lane <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Tom Lane @ 2022-03-23 01:07 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Andres Freund <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
I wrote:
> Andres Freund <[email protected]> writes:
>> There's also
>> https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=jabiru&dt=2022-03-22%2022%3A25%3A26
>> that started failing with
>> ../../preproc/ecpg --regression -I./../../include -I. -o test1.c test1.pgc
>> test1.pgc:12: ERROR: syntax error at or near "int"
>> with this commit.
> Yeah, I was just scratching my head about that.
I have a possibly-far-fetched theory: the ecpg grammar builder has
certainly never been validated against a backend grammar that
contains unused rules or unused nonterminals. Maybe it generates
a subtly incorrect .y file in such cases, and on this particular
platform that results in bad code generated by bison, and ensuing
bogus syntax errors.
The lack of other failures weakens this theory. Notably, I failed
to duplicate the problem on florican's host, which has the same
nominal bison version 3.7.6. But it wouldn't surprise me a bit
to find that OpenBSD is carrying some private patches for their
build, so maybe that matters?
In any case, I think it's a bit pointless to chase these issues
with respect to this intermediate state of the JSON patch.
Let's merge in the next step, get to a state that does not
generate build warnings, and see what happens then.
regards, tom lane
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-23 12:24 Justin Pryzby <[email protected]>
parent: Andrew Dunstan <[email protected]>
1 sibling, 1 reply; 95+ messages in thread
From: Justin Pryzby @ 2022-03-23 12:24 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
At least 0002-SQL-JSON-constructors-v64.patch has an issue with nodes,
per COPY_PARSE_PLAN_TREES.
+ERROR: unrecognized node type: 157
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-23 19:49 Andrew Dunstan <[email protected]>
parent: Justin Pryzby <[email protected]>
0 siblings, 2 replies; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-23 19:49 UTC (permalink / raw)
To: Justin Pryzby <[email protected]>; +Cc: Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 3/23/22 08:24, Justin Pryzby wrote:
> At least 0002-SQL-JSON-constructors-v64.patch has an issue with nodes,
> per COPY_PARSE_PLAN_TREES.
>
> +ERROR: unrecognized node type: 157
I just tried to reproduce this and was unable to. Ubuntu 20.04, gcc 9.4.0.
the build was done with CPPFLAGS=-DCOPY_PARSE_PLAN_TREES. To be on the
safe side I took ccache out of the equation.
Can you give me any more details?
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-23 20:23 Andrew Dunstan <[email protected]>
parent: Andrew Dunstan <[email protected]>
1 sibling, 0 replies; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-23 20:23 UTC (permalink / raw)
To: Justin Pryzby <[email protected]>; +Cc: Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 3/23/22 15:49, Andrew Dunstan wrote:
> On 3/23/22 08:24, Justin Pryzby wrote:
>> At least 0002-SQL-JSON-constructors-v64.patch has an issue with nodes,
>> per COPY_PARSE_PLAN_TREES.
>>
>> +ERROR: unrecognized node type: 157
>
>
> I just tried to reproduce this and was unable to. Ubuntu 20.04, gcc 9.4.0.
>
>
> the build was done with CPPFLAGS=-DCOPY_PARSE_PLAN_TREES. To be on the
> safe side I took ccache out of the equation.
>
>
> Can you give me any more details?
>
>
I have reproduced similar errors from patch 4 in the series.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-23 20:56 Justin Pryzby <[email protected]>
parent: Andrew Dunstan <[email protected]>
1 sibling, 1 reply; 95+ messages in thread
From: Justin Pryzby @ 2022-03-23 20:56 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On Wed, Mar 23, 2022 at 03:49:17PM -0400, Andrew Dunstan wrote:
>
> On 3/23/22 08:24, Justin Pryzby wrote:
> > At least 0002-SQL-JSON-constructors-v64.patch has an issue with nodes,
> > per COPY_PARSE_PLAN_TREES.
> >
> > +ERROR: unrecognized node type: 157
>
> I just tried to reproduce this and was unable to. Ubuntu 20.04, gcc 9.4.0.
>
> the build was done with CPPFLAGS=-DCOPY_PARSE_PLAN_TREES. To be on the
> safe side I took ccache out of the equation.
>
> Can you give me any more details?
Sorry, the issue was actually with
#define RAW_EXPRESSION_COVERAGE_TEST
make check is enough to see it:
@@ -6,181 +6,78 @@
(1 row)
SELECT JSON_OBJECT(RETURNING json);
- json_object
--------------
- {}
-(1 row)
-
+ERROR: unrecognized node type: 157
[...]
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-24 22:51 Andrew Dunstan <[email protected]>
parent: Justin Pryzby <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-24 22:51 UTC (permalink / raw)
To: Justin Pryzby <[email protected]>; +Cc: Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 3/23/22 16:56, Justin Pryzby wrote:
> On Wed, Mar 23, 2022 at 03:49:17PM -0400, Andrew Dunstan wrote:
>> On 3/23/22 08:24, Justin Pryzby wrote:
>>> At least 0002-SQL-JSON-constructors-v64.patch has an issue with nodes,
>>> per COPY_PARSE_PLAN_TREES.
>>>
>>> +ERROR: unrecognized node type: 157
>> I just tried to reproduce this and was unable to. Ubuntu 20.04, gcc 9.4.0.
>>
>> the build was done with CPPFLAGS=-DCOPY_PARSE_PLAN_TREES. To be on the
>> safe side I took ccache out of the equation.
>>
>> Can you give me any more details?
> Sorry, the issue was actually with
>
> #define RAW_EXPRESSION_COVERAGE_TEST
>
> make check is enough to see it:
>
> @@ -6,181 +6,78 @@
> (1 row)
>
> SELECT JSON_OBJECT(RETURNING json);
> - json_object
> --------------
> - {}
> -(1 row)
> -
> +ERROR: unrecognized node type: 157
> [...]
Here's a new patch set that fixes this and several similar issues.
Thanks to my colleague Masahiko Sawada for providing fixes for some of
these at very short notice.
I wonder if we should add these compile flags to the cfbot's setup?
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
Attachments:
[text/x-patch] 0001-Common-SQL-JSON-clauses-v65.patch (30.8K, ../../[email protected]/2-0001-Common-SQL-JSON-clauses-v65.patch)
download | inline diff:
From 00832f8b9c1d176426e59c3ab3651a23a8eeebe0 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:00:49 -0500
Subject: [PATCH 1/6] Common SQL/JSON clauses
---
src/backend/executor/execExpr.c | 22 ++++
src/backend/nodes/copyfuncs.c | 55 ++++++++
src/backend/nodes/equalfuncs.c | 39 ++++++
src/backend/nodes/makefuncs.c | 54 ++++++++
src/backend/nodes/nodeFuncs.c | 66 ++++++++++
src/backend/nodes/outfuncs.c | 39 ++++++
src/backend/nodes/readfuncs.c | 51 ++++++++
src/backend/optimizer/util/clauses.c | 23 ++++
src/backend/parser/gram.y | 65 +++++++++-
src/backend/parser/parse_expr.c | 181 +++++++++++++++++++++++++++
src/backend/utils/adt/ruleutils.c | 56 +++++++++
src/backend/utils/misc/queryjumble.c | 26 ++++
src/include/nodes/makefuncs.h | 5 +
src/include/nodes/nodes.h | 4 +
src/include/nodes/parsenodes.h | 13 ++
src/include/nodes/primnodes.h | 59 +++++++++
src/include/parser/kwlist.h | 2 +
17 files changed, 758 insertions(+), 2 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index e0656bfe85..d0b91e881d 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2428,6 +2428,28 @@ 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;
+ }
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d4f8455a2b..55c36b46a8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2298,6 +2298,52 @@ _copyOnConflictExpr(const OnConflictExpr *from)
return newnode;
}
+
+/*
+ * _copyJsonFormat
+ */
+static JsonFormat *
+_copyJsonFormat(const JsonFormat *from)
+{
+ JsonFormat *newnode = makeNode(JsonFormat);
+
+ COPY_SCALAR_FIELD(format_type);
+ 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;
+}
+
/* ****************************************************************
* pathnodes.h copy functions
*
@@ -5350,6 +5396,15 @@ 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;
/*
* RELATION NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f1002afe7a..0ddebd066e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -841,6 +841,36 @@ _equalOnConflictExpr(const OnConflictExpr *a, const OnConflictExpr *b)
return true;
}
+static bool
+_equalJsonFormat(const JsonFormat *a, const JsonFormat *b)
+{
+ COMPARE_SCALAR_FIELD(format_type);
+ 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;
+}
+
/*
* Stuff from pathnodes.h
*/
@@ -3358,6 +3388,15 @@ 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;
/*
* RELATION NODES
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index c85d8fe975..867a927e7a 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"
@@ -818,3 +819,56 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+/*
+ * makeJsonFormat -
+ * creates a JsonFormat node
+ */
+JsonFormat *
+makeJsonFormat(JsonFormatType type, JsonEncoding encoding, int location)
+{
+ JsonFormat *jf = makeNode(JsonFormat);
+
+ jf->format_type = type;
+ jf->encoding = encoding;
+ jf->location = location;
+
+ return jf;
+}
+
+/*
+ * makeJsonValueExpr -
+ * creates a JsonValueExpr node
+ */
+JsonValueExpr *
+makeJsonValueExpr(Expr *expr, JsonFormat *format)
+{
+ JsonValueExpr *jve = makeNode(JsonValueExpr);
+
+ jve->raw_expr = expr;
+ jve->formatted_expr = NULL;
+ jve->format = format;
+
+ return jve;
+}
+
+/*
+ * makeJsonEncoding -
+ * converts JSON encoding name to enum JsonEncoding
+ */
+JsonEncoding
+makeJsonEncoding(char *name)
+{
+ if (!pg_strcasecmp(name, "utf8"))
+ return JS_ENC_UTF8;
+ if (!pg_strcasecmp(name, "utf16"))
+ return JS_ENC_UTF16;
+ if (!pg_strcasecmp(name, "utf32"))
+ return JS_ENC_UTF32;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized JSON encoding: %s", name)));
+
+ return JS_ENC_DEFAULT;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index ec25aae6e3..0b242c76ec 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -250,6 +250,13 @@ 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;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -482,6 +489,8 @@ 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);
default:
break;
}
@@ -958,6 +967,9 @@ 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;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
@@ -1170,6 +1182,10 @@ 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;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1616,6 +1632,9 @@ 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;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2350,6 +2369,16 @@ 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;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -2680,6 +2709,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:
{
@@ -3311,6 +3341,28 @@ 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;
+ }
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -4019,6 +4071,20 @@ 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;
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 6bdad462c7..449d90c8f4 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1751,6 +1751,36 @@ _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_type, 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);
+}
+
/*****************************************************************************
*
* Stuff from pathnodes.h.
@@ -4537,6 +4567,15 @@ 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;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3f68f7c18d..6f398cdc15 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1389,6 +1389,51 @@ _readOnConflictExpr(void)
READ_DONE();
}
+/*
+ * _readJsonFormat
+ */
+static JsonFormat *
+_readJsonFormat(void)
+{
+ READ_LOCALS(JsonFormat);
+
+ READ_ENUM_FIELD(format_type, 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();
+}
+
/*
* Stuff from pathnodes.h.
*
@@ -2974,6 +3019,12 @@ 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
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 413dcac036..b9cefe8847 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -3512,6 +3512,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 0036c2f9e2..204b754eba 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -635,6 +635,14 @@ 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_output_clause_opt
+
+%type <ival> json_encoding
+ json_encoding_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
@@ -686,7 +694,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXTENSION EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
- FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
+ FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
@@ -697,7 +705,7 @@ 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
KEY
@@ -781,6 +789,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
@@ -15235,6 +15244,54 @@ opt_asymmetric: ASYMMETRIC
| /*EMPTY*/
;
+/* SQL/JSON support */
+
+json_value_expr:
+ a_expr json_format_clause_opt
+ {
+ $$ = (Node *) makeJsonValueExpr((Expr *) $1, $2);
+ }
+ ;
+
+json_format_clause_opt:
+ FORMAT json_representation
+ {
+ $$ = $2;
+ $$.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_output_clause_opt:
+ RETURNING Typename json_format_clause_opt
+ {
+ JsonOutput *n = makeNode(JsonOutput);
+ n->typeName = $2;
+ n->returning.format = $3;
+ $$ = (Node *) n;
+ }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
/*****************************************************************************
*
@@ -15776,6 +15833,7 @@ unreserved_keyword:
| FIRST_P
| FOLLOWING
| FORCE
+ | FORMAT
| FORWARD
| FUNCTION
| FUNCTIONS
@@ -15807,6 +15865,7 @@ unreserved_keyword:
| INSTEAD
| INVOKER
| ISOLATION
+ | JSON
| KEY
| LABEL
| LANGUAGE
@@ -16323,6 +16382,7 @@ bare_label_keyword:
| FOLLOWING
| FORCE
| FOREIGN
+ | FORMAT
| FORWARD
| FREEZE
| FULL
@@ -16367,6 +16427,7 @@ bare_label_keyword:
| IS
| ISOLATION
| JOIN
+ | JSON
| KEY
| LABEL
| LANGUAGE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 1c09ea24cd..985ddbedf1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -34,6 +34,7 @@
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/date.h"
+#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/timestamp.h"
#include "utils/xml.h"
@@ -3099,3 +3100,183 @@ ParseExprKindName(ParseExprKind exprKind)
}
return "unrecognized expression kind";
}
+
+/*
+ * Make string Const node from JSON encoding name.
+ *
+ * UTF8 is default encoding.
+ */
+static Const *
+getJsonEncodingConst(JsonFormat *format)
+{
+ JsonEncoding encoding;
+ const char *enc;
+ Name encname = palloc(sizeof(NameData));
+
+ if (!format ||
+ format->format_type == JS_FORMAT_DEFAULT ||
+ format->encoding == JS_ENC_DEFAULT)
+ encoding = JS_ENC_UTF8;
+ else
+ encoding = format->encoding;
+
+ switch (encoding)
+ {
+ case JS_ENC_UTF16:
+ enc = "UTF16";
+ break;
+ case JS_ENC_UTF32:
+ enc = "UTF32";
+ break;
+ case JS_ENC_UTF8:
+ enc = "UTF8";
+ break;
+ default:
+ elog(ERROR, "invalid JSON encoding: %d", encoding);
+ break;
+ }
+
+ namestrcpy(encname, enc);
+
+ return makeConst(NAMEOID, -1, InvalidOid, NAMEDATALEN,
+ NameGetDatum(encname), false, false);
+}
+
+/*
+ * Make bytea => text conversion using specified JSON format encoding.
+ */
+static Node *
+makeJsonByteaToTextConversion(Node *expr, JsonFormat *format, int location)
+{
+ Const *encoding = getJsonEncodingConst(format);
+ FuncExpr *fexpr = makeFuncExpr(F_CONVERT_FROM, TEXTOID,
+ list_make2(expr, encoding),
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+
+ fexpr->location = location;
+
+ return (Node *) fexpr;
+}
+
+/*
+ * Make CaseTestExpr node.
+ */
+static Node *
+makeCaseTestExpr(Node *expr)
+{
+ CaseTestExpr *placeholder = makeNode(CaseTestExpr);
+
+ placeholder->typeId = exprType(expr);
+ placeholder->typeMod = exprTypmod(expr);
+ placeholder->collation = exprCollation(expr);
+
+ return (Node *) placeholder;
+}
+
+/*
+ * Transform JSON value expression using specified input JSON format or
+ * default format otherwise.
+ */
+static Node *
+transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
+ JsonFormatType default_format)
+{
+ Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr);
+ Node *rawexpr;
+ JsonFormatType format;
+ Oid exprtype;
+ int location;
+ char typcategory;
+ bool typispreferred;
+
+ 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);
+
+ if (ve->format->format_type != JS_FORMAT_DEFAULT)
+ {
+ if (ve->format->encoding != JS_ENC_DEFAULT && exprtype != BYTEAOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("JSON ENCODING clause is only allowed for bytea input type"),
+ parser_errposition(pstate, ve->format->location)));
+
+ if (exprtype == JSONOID || exprtype == JSONBOID)
+ {
+ format = JS_FORMAT_DEFAULT; /* do not format json[b] types */
+ ereport(WARNING,
+ (errmsg("FORMAT JSON has no effect for json and jsonb types"),
+ parser_errposition(pstate, ve->format->location)));
+ }
+ else
+ format = ve->format->format_type;
+ }
+ else if (exprtype == JSONOID || exprtype == JSONBOID)
+ format = JS_FORMAT_DEFAULT; /* do not format json[b] types */
+ else
+ format = default_format;
+
+ if (format != JS_FORMAT_DEFAULT)
+ {
+ Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
+ Node *orig = makeCaseTestExpr(expr);
+ Node *coerced;
+
+ expr = orig;
+
+ if (exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg(ve->format->format_type == JS_FORMAT_DEFAULT ?
+ "cannot use non-string types with implicit FORMAT JSON clause" :
+ "cannot use non-string types with explicit FORMAT JSON clause"),
+ parser_errposition(pstate, ve->format->location >= 0 ?
+ ve->format->location : location)));
+
+ /* Convert encoded JSON text from bytea. */
+ if (format == JS_FORMAT_JSON && exprtype == BYTEAOID)
+ {
+ expr = makeJsonByteaToTextConversion(expr, ve->format, location);
+ exprtype = TEXTOID;
+ }
+
+ /* Try to coerce to the target type. */
+ coerced = coerce_to_target_type(pstate, expr, exprtype,
+ targettype, -1,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+
+ if (!coerced)
+ {
+ /* If coercion failed, use to_json()/to_jsonb() functions. */
+ Oid fnoid = targettype == JSONOID ? F_TO_JSON : F_TO_JSONB;
+ FuncExpr *fexpr = makeFuncExpr(fnoid, targettype,
+ list_make1(expr),
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+ fexpr->location = location;
+
+ coerced = (Node *) fexpr;
+ }
+
+ if (coerced == orig)
+ expr = rawexpr;
+ else
+ {
+ ve = copyObject(ve);
+ ve->raw_expr = (Expr *) rawexpr;
+ ve->formatted_expr = (Expr *) coerced;
+
+ expr = (Node *) ve;
+ }
+ }
+
+ return expr;
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7f4f3f7369..c7860a7580 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8266,6 +8266,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;
}
@@ -8371,6 +8376,48 @@ get_rule_expr_paren(Node *node, deparse_context *context,
appendStringInfoChar(context->buf, ')');
}
+/*
+ * get_json_format - Parse back a JsonFormat node
+ */
+static void
+get_json_format(JsonFormat *format, deparse_context *context)
+{
+ if (format->format_type == JS_FORMAT_DEFAULT)
+ return;
+
+ appendStringInfoString(context->buf,
+ format->format_type == 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(context->buf, " ENCODING %s", encoding);
+ }
+}
+
+/*
+ * get_json_returning - Parse back a JsonReturning structure
+ */
+static void
+get_json_returning(JsonReturning *returning, deparse_context *context,
+ bool json_format_by_default)
+{
+ if (!OidIsValid(returning->typid))
+ return;
+
+ appendStringInfo(context->buf, " RETURNING %s",
+ format_type_with_typemod(returning->typid,
+ returning->typmod));
+
+ if (!json_format_by_default ||
+ returning->format->format_type !=
+ (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON))
+ get_json_format(returning->format, context);
+}
/* ----------
* get_rule_expr - Parse back an expression
@@ -9531,6 +9578,15 @@ 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);
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index a67487e5fe..84435420e4 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -737,6 +737,32 @@ 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_List:
foreach(temp, (List *) node)
{
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 50de4c62af..ec8b71a685 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -106,4 +106,9 @@ 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 JsonEncoding makeJsonEncoding(char *name);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 5d075f0c34..59737f1034 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -201,6 +201,9 @@ typedef enum NodeTag
T_FromExpr,
T_OnConflictExpr,
T_IntoClause,
+ T_JsonFormat,
+ T_JsonReturning,
+ T_JsonValueExpr,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -491,6 +494,7 @@ typedef enum NodeTag
T_VacuumRelation,
T_PublicationObjSpec,
T_PublicationTable,
+ 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 2f618cb229..712e56b5f2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1553,6 +1553,19 @@ typedef struct TriggerTransition
bool isTable;
} TriggerTransition;
+/* Nodes for SQL/JSON support */
+
+/*
+ * JsonOutput -
+ * representation of JSON output clause (RETURNING type [FORMAT format])
+ */
+typedef struct JsonOutput
+{
+ NodeTag type;
+ TypeName *typeName; /* RETURNING type name, if specified */
+ JsonReturning returning; /* RETURNING FORMAT clause and type Oids */
+} JsonOutput;
+
/*****************************************************************************
* Raw Grammar Output Statements
*****************************************************************************/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 439e4b4a9d..8e3c99bdb5 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1233,6 +1233,65 @@ typedef struct XmlExpr
int location; /* token location, or -1 if unknown */
} XmlExpr;
+/*
+ * JsonEncoding -
+ * representation of JSON ENCODING clause
+ */
+typedef enum JsonEncoding
+{
+ JS_ENC_DEFAULT, /* unspecified */
+ JS_ENC_UTF8,
+ JS_ENC_UTF16,
+ JS_ENC_UTF32,
+} JsonEncoding;
+
+/*
+ * JsonFormatType -
+ * enumeration of JSON formats used in JSON FORMAT clause
+ */
+typedef enum JsonFormatType
+{
+ JS_FORMAT_DEFAULT, /* unspecified */
+ JS_FORMAT_JSON, /* FORMAT JSON [ENCODING ...] */
+ JS_FORMAT_JSONB /* implicit internal format for RETURNING jsonb */
+} JsonFormatType;
+
+/*
+ * JsonFormat -
+ * representation of JSON FORMAT clause
+ */
+typedef struct JsonFormat
+{
+ NodeTag type;
+ JsonFormatType format_type; /* format type */
+ JsonEncoding encoding; /* JSON encoding */
+ int location; /* token location, or -1 if unknown */
+} JsonFormat;
+
+/*
+ * JsonReturning -
+ * transformed representation of JSON RETURNING clause
+ */
+typedef struct JsonReturning
+{
+ NodeTag type;
+ JsonFormat *format; /* output JSON format */
+ Oid typid; /* target type Oid */
+ int32 typmod; /* target type modifier */
+} JsonReturning;
+
+/*
+ * JsonValueExpr -
+ * representation of JSON value expression (expr [FORMAT json_format])
+ */
+typedef struct JsonValueExpr
+{
+ NodeTag type;
+ Expr *raw_expr; /* raw expression */
+ Expr *formatted_expr; /* formatted expression or NULL */
+ JsonFormat *format; /* FORMAT clause, if specified */
+} JsonValueExpr;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index bcef7eed2f..f3502b8be4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -175,6 +175,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,6 +228,7 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL)
PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD, BARE_LABEL)
--
2.25.1
[text/x-patch] 0002-SQL-JSON-constructors-v65.patch (187.0K, ../../[email protected]/3-0002-SQL-JSON-constructors-v65.patch)
download | inline diff:
From bde5f7c6156347c2d9923ddebe03da32715d17f8 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:02:10 -0500
Subject: [PATCH 2/6] SQL/JSON constructors
---
doc/src/sgml/func.sgml | 947 +++++++++++++++++++++++
src/backend/executor/execExpr.c | 63 ++
src/backend/executor/execExprInterp.c | 48 ++
src/backend/jit/llvm/llvmjit_expr.c | 6 +
src/backend/jit/llvm/llvmjit_types.c | 1 +
src/backend/nodes/copyfuncs.c | 173 +++++
src/backend/nodes/equalfuncs.c | 132 ++++
src/backend/nodes/makefuncs.c | 15 +
src/backend/nodes/nodeFuncs.c | 152 ++++
src/backend/nodes/outfuncs.c | 18 +
src/backend/nodes/readfuncs.c | 22 +
src/backend/optimizer/util/clauses.c | 23 +
src/backend/parser/gram.y | 265 ++++++-
src/backend/parser/parse_expr.c | 589 ++++++++++++++
src/backend/parser/parse_target.c | 13 +
src/backend/parser/parser.c | 16 +
src/backend/utils/adt/json.c | 422 +++++++++-
src/backend/utils/adt/jsonb.c | 224 +++++-
src/backend/utils/adt/jsonb_util.c | 24 +-
src/backend/utils/adt/ruleutils.c | 212 ++++-
src/backend/utils/misc/queryjumble.c | 12 +
src/include/catalog/pg_aggregate.dat | 22 +
src/include/catalog/pg_proc.dat | 70 ++
src/include/executor/execExpr.h | 14 +
src/include/nodes/makefuncs.h | 1 +
src/include/nodes/nodes.h | 8 +
src/include/nodes/parsenodes.h | 96 ++-
src/include/nodes/primnodes.h | 25 +
src/include/parser/kwlist.h | 6 +
src/include/utils/json.h | 6 +
src/include/utils/jsonb.h | 9 +
src/interfaces/ecpg/preproc/parse.pl | 2 +
src/interfaces/ecpg/preproc/parser.c | 14 +
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/expected/sqljson.out | 746 ++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/opr_sanity.sql | 6 +-
src/test/regress/sql/sqljson.sql | 282 +++++++
38 files changed, 4562 insertions(+), 130 deletions(-)
create mode 100644 src/test/regress/expected/sqljson.out
create mode 100644 src/test/regress/sql/sqljson.sql
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8a802fb225..3124f87ceb 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17564,6 +17564,844 @@ $.* ? (@ 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>
+
+ <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 or JSON arrays represented as
+ SQL character or binary strings.
+ </para>
+
+ <itemizedlist>
+ <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-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="sqljson-common-clauses">
+ <title>SQL/JSON Common Clauses</title>
+
+ <sect4 id="sqljson-output-clause">
+ <title>SQL/JSON Output Clause</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable> [ FORMAT JSON [ ENCODING UTF8 ] ]</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">
@@ -19719,6 +20557,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/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index d0b91e881d..a9547aaef1 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2450,6 +2450,69 @@ ExecInitExprRec(Expr *node, ExprState *state,
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
+ {
+ 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++;
+ }
+
+ 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;
+
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 64bd17b62e..f2a0821a7a 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -71,6 +71,8 @@
#include "utils/date.h"
#include "utils/datum.h"
#include "utils/expandedrecord.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -477,6 +479,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_GROUPING_FUNC,
&&CASE_EEOP_WINDOW_FUNC,
&&CASE_EEOP_SUBPLAN,
+ &&CASE_EEOP_JSON_CONSTRUCTOR,
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
&&CASE_EEOP_AGG_DESERIALIZE,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1786,7 +1789,13 @@ 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();
}
@@ -4380,3 +4389,42 @@ 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_type == 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
+ {
+ res = (Datum) 0;
+ elog(ERROR, "invalid JsonConstructorExpr type %d", ctor->type);
+ }
+
+ *op->resvalue = res;
+ *op->resnull = isnull;
+}
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index bd86f546d7..d0c26cf58b 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2348,6 +2348,12 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_JSON_CONSTRUCTOR:
+ build_EvalXFunc(b, mod, "ExecEvalJsonConstructor",
+ v_state, op, v_econtext);
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ break;
+
case EEOP_LAST:
Assert(false);
break;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index d5191cf02b..53c75dd9d6 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -131,6 +131,7 @@ void *referenced_functions[] =
ExecEvalSysVar,
ExecEvalWholeRowVar,
ExecEvalXmlExpr,
+ ExecEvalJsonConstructor,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 55c36b46a8..d89596a74a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2344,6 +2344,152 @@ _copyJsonValueExpr(const JsonValueExpr *from)
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;
+}
+
/* ****************************************************************
* pathnodes.h copy functions
*
@@ -5405,6 +5551,33 @@ copyObjectImpl(const void *from)
case T_JsonValueExpr:
retval = _copyJsonValueExpr(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;
/*
* RELATION NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0ddebd066e..9cb8495ddf 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -871,6 +871,111 @@ _equalJsonValueExpr(const JsonValueExpr *a, const JsonValueExpr *b)
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;
+}
+
/*
* Stuff from pathnodes.h
*/
@@ -3397,6 +3502,9 @@ equal(const void *a, const void *b)
case T_JsonValueExpr:
retval = _equalJsonValueExpr(a, b);
break;
+ case T_JsonConstructorExpr:
+ retval = _equalJsonConstructorExpr(a, b);
+ break;
/*
* RELATION NODES
@@ -3977,6 +4085,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 867a927e7a..7b4f7972e6 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -872,3 +872,18 @@ makeJsonEncoding(char *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;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 0b242c76ec..25cf282aab 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -257,6 +257,9 @@ exprType(const Node *expr)
type = exprType((Node *) (jve->formatted_expr ? jve->formatted_expr : jve->raw_expr));
}
break;
+ case T_JsonConstructorExpr:
+ type = ((const JsonConstructorExpr *) expr)->returning->typid;
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -491,6 +494,8 @@ exprTypmod(const Node *expr)
return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
case T_JsonValueExpr:
return exprTypmod((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
+ case T_JsonConstructorExpr:
+ return -1; /* ((const JsonConstructorExpr *) expr)->returning->typmod; */
default:
break;
}
@@ -970,6 +975,16 @@ exprCollation(const Node *expr)
case T_JsonValueExpr:
coll = exprCollation((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
break;
+ case T_JsonConstructorExpr:
+ {
+ const JsonConstructorExpr *ctor = (const JsonConstructorExpr *) expr;
+
+ if (ctor->coercion)
+ coll = exprCollation((Node *) ctor->coercion);
+ else
+ coll = InvalidOid;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
@@ -1186,6 +1201,16 @@ exprSetCollation(Node *expr, Oid collation)
exprSetCollation((Node *) ((JsonValueExpr *) expr)->formatted_expr,
collation);
break;
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *ctor = (JsonConstructorExpr *) expr;
+
+ if (ctor->coercion)
+ exprSetCollation((Node *) ctor->coercion, collation);
+ else
+ Assert(!OidIsValid(collation)); /* result is always a json[b] type */
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1635,6 +1660,9 @@ exprLocation(const Node *expr)
case T_JsonValueExpr:
loc = exprLocation((Node *) ((const JsonValueExpr *) expr)->raw_expr);
break;
+ case T_JsonConstructorExpr:
+ loc = ((const JsonConstructorExpr *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2379,6 +2407,18 @@ expression_tree_walker(Node *node,
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;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -3361,6 +3401,19 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->formatted_expr, jve->formatted_expr, Expr *);
MUTATE(newnode->format, jve->format, JsonFormat *);
+ return (Node *) newnode;
+ }
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *jve = (JsonConstructorExpr *) node;
+ JsonConstructorExpr *newnode;
+
+ FLATCOPY(newnode, jve, JsonConstructorExpr);
+ MUTATE(newnode->args, jve->args, List *);
+ MUTATE(newnode->func, jve->func, Expr *);
+ MUTATE(newnode->coercion, jve->coercion, Expr *);
+ MUTATE(newnode->returning, jve->returning, JsonReturning *);
+
return (Node *) newnode;
}
default:
@@ -3637,6 +3690,7 @@ raw_expression_tree_walker(Node *node,
case T_ParamRef:
case T_A_Const:
case T_A_Star:
+ case T_JsonFormat:
/* primitive node types with no subnodes */
break;
case T_Alias:
@@ -4085,6 +4139,104 @@ raw_expression_tree_walker(Node *node,
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;
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 449d90c8f4..c25f0bd684 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1781,6 +1781,21 @@ _outJsonValueExpr(StringInfo str, const JsonValueExpr *node)
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);
+}
+
/*****************************************************************************
*
* Stuff from pathnodes.h.
@@ -4576,6 +4591,9 @@ outNode(StringInfo str, const void *obj)
case T_JsonValueExpr:
_outJsonValueExpr(str, obj);
break;
+ case T_JsonConstructorExpr:
+ _outJsonConstructorExpr(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6f398cdc15..e0b3ad1ed2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1434,6 +1434,26 @@ _readJsonValueExpr(void)
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();
+}
+
/*
* Stuff from pathnodes.h.
*
@@ -3025,6 +3045,8 @@ parseNodeString(void)
return_value = _readJsonReturning();
else if (MATCH("JSONVALUEEXPR", 13))
return_value = _readJsonValueExpr();
+ else if (MATCH("JSONCTOREXPR", 12))
+ return_value = _readJsonConstructorExpr();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index b9cefe8847..e1147c431e 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -50,6 +50,8 @@
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
@@ -382,6 +384,27 @@ contain_mutable_functions_walker(Node *node, void *context)
context))
return true;
+ if (IsA(node, JsonConstructorExpr))
+ {
+ const JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+ ListCell *lc;
+ bool is_jsonb =
+ ctor->returning->format->format_type == 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, SQLValueFunction))
{
/* all variants of SQLValueFunction are stable */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 204b754eba..055df94bcc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -638,11 +638,31 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> json_format_clause_opt
json_representation
json_value_expr
+ json_func_expr
json_output_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
+
+%type <list> json_name_and_value_list
+ json_value_expr_list
+ json_array_aggregate_order_by_clause_opt
%type <ival> json_encoding
json_encoding_clause_opt
+%type <boolean> json_key_uniqueness_constraint_opt
+ json_object_constructor_null_clause_opt
+ json_array_constructor_null_clause_opt
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
* They must be listed first so that their numeric codes do not depend on
@@ -668,7 +688,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
@@ -705,9 +725,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
- JOIN JSON
+ JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG
- KEY
+ KEY KEYS
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
@@ -771,7 +791,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
@@ -825,11 +845,13 @@ 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 ABSENT UNIQUE
%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 ] */
/* Unary Operators */
%left AT /* sets precedence for AT TIME ZONE */
%left COLLATE
@@ -847,6 +869,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
+
%%
/*
@@ -13381,7 +13406,7 @@ ConstInterval:
opt_timezone:
WITH_LA TIME ZONE { $$ = true; }
- | WITHOUT TIME ZONE { $$ = false; }
+ | WITHOUT_LA TIME ZONE { $$ = false; }
| /*EMPTY*/ { $$ = false; }
;
@@ -13994,6 +14019,17 @@ b_expr: c_expr
}
;
+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.
*
@@ -14246,6 +14282,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; }
;
@@ -14259,6 +14304,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; }
;
/*
@@ -14546,6 +14592,8 @@ func_expr_common_subexpr:
n->location = @1;
$$ = (Node *)n;
}
+ | json_func_expr
+ { $$ = $1; }
;
/*
@@ -15245,11 +15293,14 @@ opt_asymmetric: ASYMMETRIC
;
/* SQL/JSON support */
+json_func_expr:
+ json_value_constructor
+ ;
json_value_expr:
a_expr json_format_clause_opt
{
- $$ = (Node *) makeJsonValueExpr((Expr *) $1, $2);
+ $$ = (Node *) makeJsonValueExpr((Expr *) $1, castNode(JsonFormat, $2));
}
;
@@ -15257,7 +15308,7 @@ json_format_clause_opt:
FORMAT json_representation
{
$$ = $2;
- $$.location = @1;
+ castNode(JsonFormat, $$)->location = @1;
}
| /* EMPTY */
{
@@ -15287,10 +15338,196 @@ json_output_clause_opt:
{
JsonOutput *n = makeNode(JsonOutput);
n->typeName = $2;
- n->returning.format = $3;
+ n->returning = makeNode(JsonReturning);
+ n->returning->format = (JsonFormat *) $3;
$$ = (Node *) n;
}
| /* EMPTY */ { $$ = NULL; }
+ ;
+
+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; }
;
/*****************************************************************************
@@ -15737,6 +15974,7 @@ BareColLabel: IDENT { $$ = $1; }
*/
unreserved_keyword:
ABORT_P
+ | ABSENT
| ABSOLUTE_P
| ACCESS
| ACTION
@@ -15867,6 +16105,7 @@ unreserved_keyword:
| ISOLATION
| JSON
| KEY
+ | KEYS
| LABEL
| LANGUAGE
| LARGE_P
@@ -16075,6 +16314,10 @@ col_name_keyword:
| INT_P
| INTEGER
| INTERVAL
+ | JSON_ARRAY
+ | JSON_ARRAYAGG
+ | JSON_OBJECT
+ | JSON_OBJECTAGG
| LEAST
| NATIONAL
| NCHAR
@@ -16243,6 +16486,7 @@ reserved_keyword:
*/
bare_label_keyword:
ABORT_P
+ | ABSENT
| ABSOLUTE_P
| ACCESS
| ACTION
@@ -16428,7 +16672,12 @@ bare_label_keyword:
| ISOLATION
| JOIN
| JSON
+ | JSON_ARRAY
+ | JSON_ARRAYAGG
+ | JSON_OBJECT
+ | JSON_OBJECTAGG
| KEY
+ | KEYS
| LABEL
| LANGUAGE
| LARGE_P
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 985ddbedf1..6b93a76bca 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"
@@ -75,6 +77,14 @@ static Node *transformWholeRowRef(ParseState *pstate,
static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformJsonObjectConstructor(ParseState *pstate,
+ JsonObjectConstructor *ctor);
+static Node *transformJsonArrayConstructor(ParseState *pstate,
+ JsonArrayConstructor *ctor);
+static Node *transformJsonArrayQueryConstructor(ParseState *pstate,
+ JsonArrayQueryConstructor *ctor);
+static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg);
+static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg);
static Node *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -302,6 +312,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
break;
}
+ case T_JsonObjectConstructor:
+ result = transformJsonObjectConstructor(pstate, (JsonObjectConstructor *) expr);
+ break;
+
+ case T_JsonArrayConstructor:
+ result = transformJsonArrayConstructor(pstate, (JsonArrayConstructor *) expr);
+ break;
+
+ case T_JsonArrayQueryConstructor:
+ result = transformJsonArrayQueryConstructor(pstate, (JsonArrayQueryConstructor *) expr);
+ break;
+
+ case T_JsonObjectAgg:
+ result = transformJsonObjectAgg(pstate, (JsonObjectAgg *) expr);
+ break;
+
+ case T_JsonArrayAgg:
+ result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3280,3 +3310,562 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
return expr;
}
+
+/*
+ * Checks specified output format for its applicability to the target type.
+ */
+static void
+checkJsonOutputFormat(ParseState *pstate, const JsonFormat *format,
+ Oid targettype, bool allow_format_for_non_strings)
+{
+ if (!allow_format_for_non_strings &&
+ format->format_type != JS_FORMAT_DEFAULT &&
+ (targettype != BYTEAOID &&
+ targettype != JSONOID &&
+ targettype != JSONBOID))
+ {
+ char typcategory;
+ bool typispreferred;
+
+ get_type_category_preferred(targettype, &typcategory, &typispreferred);
+
+ if (typcategory != TYPCATEGORY_STRING)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ parser_errposition(pstate, format->location),
+ errmsg("cannot use JSON format with non-string output types")));
+ }
+
+ if (format->format_type == JS_FORMAT_JSON)
+ {
+ JsonEncoding enc = format->encoding != JS_ENC_DEFAULT ?
+ format->encoding : JS_ENC_UTF8;
+
+ if (targettype != BYTEAOID &&
+ format->encoding != JS_ENC_DEFAULT)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ parser_errposition(pstate, format->location),
+ errmsg("cannot set JSON encoding for non-bytea output types")));
+
+ if (enc != JS_ENC_UTF8)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported JSON encoding"),
+ errhint("only UTF8 JSON encoding is supported"),
+ parser_errposition(pstate, format->location)));
+ }
+}
+
+/*
+ * Transform JSON output clause.
+ *
+ * Assigns target type oid and modifier.
+ * Assigns default format or checks specified format for its applicability to
+ * the target type.
+ */
+static JsonReturning *
+transformJsonOutput(ParseState *pstate, const JsonOutput *output,
+ bool allow_format)
+{
+ JsonReturning *ret;
+
+ /* if output clause is not specified, make default clause value */
+ if (!output)
+ {
+ ret = makeNode(JsonReturning);
+
+ ret->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+ ret->typid = InvalidOid;
+ ret->typmod = -1;
+
+ return ret;
+ }
+
+ ret = copyObject(output->returning);
+
+ typenameTypeIdAndMod(pstate, output->typeName, &ret->typid, &ret->typmod);
+
+ if (output->typeName->setof)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning SETOF types is not supported in SQL/JSON functions")));
+
+ if (ret->format->format_type == JS_FORMAT_DEFAULT)
+ /* assign JSONB format when returning jsonb, or JSON format otherwise */
+ ret->format->format_type =
+ ret->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON;
+ else
+ checkJsonOutputFormat(pstate, ret->format, ret->typid, allow_format);
+
+ return ret;
+}
+
+/*
+ * Transform JSON output clause of JSON 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_type = JS_FORMAT_JSONB;
+ }
+ else
+ {
+ /* Note: this includes the have_json case */
+
+ /* XXX TEXT is default by the standard, but we return JSON */
+ returning->typid = JSONOID;
+ returning->format->format_type = JS_FORMAT_JSON;
+ }
+
+ returning->typmod = -1;
+ }
+
+ return returning;
+}
+
+/*
+ * Coerce json[b]-valued function expression to the output type.
+ */
+static Node *
+coerceJsonFuncExpr(ParseState *pstate, Node *expr,
+ const JsonReturning *returning, bool report_error)
+{
+ Node *res;
+ int location;
+ Oid exprtype = exprType(expr);
+
+ /* if output type is not specified or equals to function type, return */
+ if (!OidIsValid(returning->typid) || returning->typid == exprtype)
+ return expr;
+
+ location = exprLocation(expr);
+
+ if (location < 0)
+ location = returning ? returning->format->location : -1;
+
+ /* special case for RETURNING bytea FORMAT json */
+ if (returning->format->format_type == JS_FORMAT_JSON &&
+ returning->typid == BYTEAOID)
+ {
+ /* encode json text into bytea using pg_convert_to() */
+ Node *texpr = coerce_to_specific_type(pstate, expr, TEXTOID,
+ "JSON_FUNCTION");
+ Const *enc = getJsonEncodingConst(returning->format);
+ FuncExpr *fexpr = makeFuncExpr(F_CONVERT_TO, BYTEAOID,
+ list_make2(texpr, enc),
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+ fexpr->location = location;
+
+ return (Node *) fexpr;
+ }
+
+ /* try to coerce expression to the output type */
+ res = coerce_to_target_type(pstate, expr, exprtype,
+ returning->typid, returning->typmod,
+ /* XXX throwing errors when casting to char(N) */
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+
+ if (!res && report_error)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprtype),
+ format_type_be(returning->typid)),
+ parser_coercion_errposition(pstate, location, expr)));
+
+ return res;
+}
+
+static Node *
+makeJsonConstructorExpr(ParseState *pstate, JsonConstructorType type,
+ List *args, Expr *fexpr, JsonReturning *returning,
+ bool unique, bool absent_on_null, int location)
+{
+ JsonConstructorExpr *jsctor = makeNode(JsonConstructorExpr);
+ Node *placeholder;
+ Node *coercion;
+ Oid intermediate_typid =
+ returning->format->format_type == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
+
+ jsctor->args = args;
+ jsctor->func = fexpr;
+ jsctor->type = type;
+ jsctor->returning = returning;
+ jsctor->unique = unique;
+ jsctor->absent_on_null = absent_on_null;
+ jsctor->location = location;
+
+ if (fexpr)
+ placeholder = makeCaseTestExpr((Node *) fexpr);
+ else
+ {
+ CaseTestExpr *cte = makeNode(CaseTestExpr);
+
+ cte->typeId = intermediate_typid;
+ cte->typeMod = -1;
+ cte->collation = InvalidOid;
+
+ placeholder = (Node *) cte;
+ }
+
+ coercion = coerceJsonFuncExpr(pstate, placeholder, returning, true);
+
+ if (coercion != placeholder)
+ jsctor->coercion = (Expr *) coercion;
+
+ return (Node *) jsctor;
+}
+
+/*
+ * Transform JSON_OBJECT() constructor.
+ *
+ * JSON_OBJECT() is transformed into json[b]_build_object[_ext]() call
+ * depending on the output JSON format. The first two arguments of
+ * json[b]_build_object_ext() are absent_on_null and check_key_uniqueness.
+ *
+ * Then function call result is coerced to the target type.
+ */
+static Node *
+transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor)
+{
+ JsonReturning *returning;
+ List *args = NIL;
+
+ /* transform key-value pairs, if any */
+ if (ctor->exprs)
+ {
+ ListCell *lc;
+
+ /* transform and append key-value arguments */
+ foreach(lc, ctor->exprs)
+ {
+ JsonKeyValue *kv = castNode(JsonKeyValue, lfirst(lc));
+ Node *key = transformExprRecurse(pstate, (Node *) kv->key);
+ Node *val = transformJsonValueExpr(pstate, kv->value,
+ JS_FORMAT_DEFAULT);
+
+ args = lappend(args, key);
+ args = lappend(args, val);
+ }
+ }
+
+ returning = transformJsonConstructorOutput(pstate, ctor->output, args);
+
+ return makeJsonConstructorExpr(pstate, JSCTOR_JSON_OBJECT, args, NULL,
+ returning, ctor->unique,
+ ctor->absent_on_null, ctor->location);
+}
+
+/*
+ * Transform JSON_ARRAY(query [FORMAT] [RETURNING] [ON NULL]) into
+ * (SELECT JSON_ARRAYAGG(a [FORMAT] [RETURNING] [ON NULL]) FROM (query) q(a))
+ */
+static Node *
+transformJsonArrayQueryConstructor(ParseState *pstate,
+ JsonArrayQueryConstructor *ctor)
+{
+ SubLink *sublink = makeNode(SubLink);
+ SelectStmt *select = makeNode(SelectStmt);
+ RangeSubselect *range = makeNode(RangeSubselect);
+ Alias *alias = makeNode(Alias);
+ ResTarget *target = makeNode(ResTarget);
+ JsonArrayAgg *agg = makeNode(JsonArrayAgg);
+ ColumnRef *colref = makeNode(ColumnRef);
+ Query *query;
+ ParseState *qpstate;
+
+ /* Transform query only for counting target list entries. */
+ qpstate = make_parsestate(pstate);
+
+ query = transformStmt(qpstate, ctor->query);
+
+ if (count_nonjunk_tlist_entries(query->targetList) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("subquery must return only one column"),
+ parser_errposition(pstate, ctor->location)));
+
+ free_parsestate(qpstate);
+
+ colref->fields = list_make2(makeString(pstrdup("q")),
+ makeString(pstrdup("a")));
+ colref->location = ctor->location;
+
+ agg->arg = makeJsonValueExpr((Expr *) colref, ctor->format);
+ agg->absent_on_null = ctor->absent_on_null;
+ agg->constructor = makeNode(JsonAggConstructor);
+ agg->constructor->agg_order = NIL;
+ agg->constructor->output = ctor->output;
+ agg->constructor->location = ctor->location;
+
+ target->name = NULL;
+ target->indirection = NIL;
+ target->val = (Node *) agg;
+ target->location = ctor->location;
+
+ alias->aliasname = pstrdup("q");
+ alias->colnames = list_make1(makeString(pstrdup("a")));
+
+ range->lateral = false;
+ range->subquery = ctor->query;
+ range->alias = alias;
+
+ select->targetList = list_make1(target);
+ select->fromClause = list_make1(range);
+
+ sublink->subLinkType = EXPR_SUBLINK;
+ sublink->subLinkId = 0;
+ sublink->testexpr = NULL;
+ sublink->operName = NIL;
+ sublink->subselect = (Node *) select;
+ sublink->location = ctor->location;
+
+ return transformExprRecurse(pstate, (Node *) sublink);
+}
+
+/*
+ * Common code for JSON_OBJECTAGG and JSON_ARRAYAGG transformation.
+ */
+static Node *
+transformJsonAggConstructor(ParseState *pstate, JsonAggConstructor *agg_ctor,
+ JsonReturning *returning, List *args,
+ const char *aggfn, Oid aggtype,
+ JsonConstructorType ctor_type,
+ bool unique, bool absent_on_null)
+{
+ Oid aggfnoid;
+ Node *node;
+ Expr *aggfilter = agg_ctor->agg_filter ? (Expr *)
+ transformWhereClause(pstate, agg_ctor->agg_filter,
+ EXPR_KIND_FILTER, "FILTER") : NULL;
+
+ aggfnoid = DatumGetInt32(DirectFunctionCall1(regprocin,
+ CStringGetDatum(aggfn)));
+
+ if (agg_ctor->over)
+ {
+ /* window function */
+ WindowFunc *wfunc = makeNode(WindowFunc);
+
+ wfunc->winfnoid = aggfnoid;
+ wfunc->wintype = aggtype;
+ /* wincollid and inputcollid will be set by parse_collate.c */
+ wfunc->args = args;
+ /* winref will be set by transformWindowFuncCall */
+ wfunc->winstar = false;
+ wfunc->winagg = true;
+ wfunc->aggfilter = aggfilter;
+ wfunc->location = agg_ctor->location;
+
+ /*
+ * ordered aggs not allowed in windows yet
+ */
+ if (agg_ctor->agg_order != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("aggregate ORDER BY is not implemented for window functions"),
+ parser_errposition(pstate, agg_ctor->location)));
+
+ /* parse_agg.c does additional window-func-specific processing */
+ transformWindowFuncCall(pstate, wfunc, agg_ctor->over);
+
+ node = (Node *) wfunc;
+ }
+ else
+ {
+ Aggref *aggref = makeNode(Aggref);
+
+ aggref->aggfnoid = aggfnoid;
+ aggref->aggtype = aggtype;
+
+ /* aggcollid and inputcollid will be set by parse_collate.c */
+ aggref->aggtranstype = InvalidOid; /* will be set by planner */
+ /* aggargtypes will be set by transformAggregateCall */
+ /* aggdirectargs and args will be set by transformAggregateCall */
+ /* aggorder and aggdistinct will be set by transformAggregateCall */
+ aggref->aggfilter = aggfilter;
+ aggref->aggstar = false;
+ aggref->aggvariadic = false;
+ aggref->aggkind = AGGKIND_NORMAL;
+ /* agglevelsup will be set by transformAggregateCall */
+ aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */
+ aggref->location = agg_ctor->location;
+
+ transformAggregateCall(pstate, aggref, args, agg_ctor->agg_order, false);
+
+ node = (Node *) aggref;
+ }
+
+ return makeJsonConstructorExpr(pstate, ctor_type, NIL, (Expr *) node,
+ returning, unique, absent_on_null,
+ agg_ctor->location);
+}
+
+/*
+ * Transform JSON_OBJECTAGG() aggregate function.
+ *
+ * JSON_OBJECTAGG() is transformed into
+ * json[b]_objectagg(key, value, absent_on_null, check_unique) call depending on
+ * the output JSON format. Then the function call result is coerced to the
+ * target output type.
+ */
+static Node *
+transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg)
+{
+ JsonReturning *returning;
+ Node *key;
+ Node *val;
+ List *args;
+ const char *aggfnname;
+ Oid aggtype;
+
+ key = transformExprRecurse(pstate, (Node *) agg->arg->key);
+ val = transformJsonValueExpr(pstate, agg->arg->value, JS_FORMAT_DEFAULT);
+ args = list_make2(key, val);
+
+ returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
+ args);
+
+ if (returning->format->format_type == JS_FORMAT_JSONB)
+ {
+ if (agg->absent_on_null)
+ if (agg->unique)
+ aggfnname = "pg_catalog.jsonb_object_agg_unique_strict"; /* 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 = transformJsonValueExpr(pstate, agg->arg, JS_FORMAT_DEFAULT);
+
+ returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
+ list_make1(arg));
+
+ if (returning->format->format_type == JS_FORMAT_JSONB)
+ {
+ aggfnname = agg->absent_on_null ?
+ "pg_catalog.jsonb_agg_strict" : "pg_catalog.jsonb_agg";
+ aggtype = JSONBOID;
+ }
+ else
+ {
+ aggfnname = agg->absent_on_null ?
+ "pg_catalog.json_agg_strict" : "pg_catalog.json_agg";
+ aggtype = JSONOID;
+ }
+
+ return transformJsonAggConstructor(pstate, agg->constructor, returning,
+ list_make1(arg), aggfnname, aggtype,
+ JSCTOR_JSON_ARRAYAGG,
+ false, agg->absent_on_null);
+}
+
+/*
+ * Transform JSON_ARRAY() constructor.
+ *
+ * JSON_ARRAY() is transformed into json[b]_build_array[_ext]() call
+ * depending on the output JSON format. The first argument of
+ * json[b]_build_array_ext() is absent_on_null.
+ *
+ * Then function call result is coerced to the target type.
+ */
+static Node *
+transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor)
+{
+ JsonReturning *returning;
+ List *args = NIL;
+
+ /* transform element expressions, if any */
+ if (ctor->exprs)
+ {
+ ListCell *lc;
+
+ /* transform and append element arguments */
+ foreach(lc, ctor->exprs)
+ {
+ JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc));
+ Node *val = transformJsonValueExpr(pstate, jsval,
+ JS_FORMAT_DEFAULT);
+
+ args = lappend(args, val);
+ }
+ }
+
+ returning = transformJsonConstructorOutput(pstate, ctor->output, args);
+
+ return makeJsonConstructorExpr(pstate, JSCTOR_JSON_ARRAY, args, NULL,
+ returning, false, ctor->absent_on_null,
+ ctor->location);
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 059eeb9e94..204d285773 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1957,6 +1957,19 @@ FigureColnameInternal(Node *node, char **name)
case T_XmlSerialize:
*name = "xmlserialize";
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;
default:
break;
}
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 50227cc098..eee0a29c08 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/json.c b/src/backend/utils/adt/json.c
index 7879f342e6..d088fafc56 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -13,7 +13,9 @@
*/
#include "postgres.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "common/hashfn.h"
#include "funcapi.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
@@ -42,6 +44,42 @@ typedef enum /* type categories for datum_to_json */
JSONTYPE_OTHER /* all else */
} JsonTypeCategory;
+/* 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
+{
+ 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
{
StringInfo str;
@@ -49,6 +87,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,
@@ -722,6 +761,38 @@ row_to_json_pretty(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(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)
*/
@@ -754,8 +825,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 +866,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 +885,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 +903,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 +945,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 +1081,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 +1112,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
else
{
state = (JsonAggState *) PG_GETARG_POINTER(0);
- appendStringInfoString(state->str, ", ");
}
/*
@@ -925,11 +1127,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 +1183,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 +1236,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 +1262,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 +1283,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 +1344,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 +1378,13 @@ json_build_object_noargs(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
}
-/*
- * SQL function json_build_array(variadic "any")
- */
Datum
-json_build_array(PG_FUNCTION_ARGS)
+json_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types,
+ bool absent_on_null)
{
- int nargs;
int i;
const char *sep = "";
StringInfo result;
- Datum *args;
- bool *nulls;
- Oid *types;
-
- /* fetch argument values to build the array */
- nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls);
-
- if (nargs < 0)
- PG_RETURN_NULL();
result = makeStringInfo();
@@ -1076,6 +1392,9 @@ json_build_array(PG_FUNCTION_ARGS)
for (i = 0; i < nargs; i++)
{
+ if (absent_on_null && nulls[i])
+ continue;
+
appendStringInfoString(result, sep);
sep = ", ";
add_json(args[i], nulls[i], result, types[i], false);
@@ -1083,7 +1402,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));
}
/*
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index f5f40a94bd..a103cbc7c6 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"
@@ -1126,6 +1127,39 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result,
datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar);
}
+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)
*/
@@ -1153,24 +1187,12 @@ to_jsonb(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(JsonbValueToJsonb(result.res));
}
-/*
- * SQL function jsonb_build_object(variadic "any")
- */
Datum
-jsonb_build_object(PG_FUNCTION_ARGS)
+jsonb_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types,
+ bool absent_on_null, bool unique_keys)
{
- int nargs;
int i;
JsonbInState result;
- Datum *args;
- bool *nulls;
- Oid *types;
-
- /* build argument values to build the object */
- nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
-
- if (nargs < 0)
- PG_RETURN_NULL();
if (nargs % 2 != 0)
ereport(ERROR,
@@ -1183,15 +1205,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 +1233,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 +1271,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 +1555,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 +1568,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 +1617,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 +1689,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 +1739,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 +1755,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
*jbval;
JsonbValue v;
JsonbIteratorToken type;
+ bool skip;
if (!AggCheckCallContext(fcinfo, &aggcontext))
{
@@ -1692,6 +1775,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 +1813,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 +1877,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 +1959,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)
{
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 60442758b3..aa151a53d6 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -64,7 +64,8 @@ static int lengthCompareJsonbStringValue(const void *a, const void *b);
static int lengthCompareJsonbString(const char *val1, int len1,
const char *val2, int len2);
static int lengthCompareJsonbPair(const void *a, const void *b, void *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);
@@ -689,7 +690,9 @@ pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq,
appendElement(*pstate, scalarVal);
break;
case WJB_END_OBJECT:
- uniqueifyJsonbObject(&(*pstate)->contVal);
+ uniqueifyJsonbObject(&(*pstate)->contVal,
+ (*pstate)->unique_keys,
+ (*pstate)->skip_nulls);
/* fall through! */
case WJB_END_ARRAY:
/* Steps here common to WJB_END_OBJECT case */
@@ -732,6 +735,9 @@ pushState(JsonbParseState **pstate)
JsonbParseState *ns = palloc(sizeof(JsonbParseState));
ns->next = *pstate;
+ ns->unique_keys = false;
+ ns->skip_nulls = false;
+
return ns;
}
@@ -1936,7 +1942,7 @@ lengthCompareJsonbPair(const void *a, const void *b, void *binequal)
* Sort and unique-ify pairs in JsonbValue object
*/
static void
-uniqueifyJsonbObject(JsonbValue *object)
+uniqueifyJsonbObject(JsonbValue *object, bool unique_keys, bool skip_nulls)
{
bool hasNonUniq = false;
@@ -1946,15 +1952,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/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c7860a7580..6db6c008dd 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -457,6 +457,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);
@@ -6245,7 +6251,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, '(');
@@ -8093,6 +8100,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_GroupingFunc:
case T_WindowFunc:
case T_FuncExpr:
+ case T_JsonConstructorExpr:
/* function-like: name(..) or name[..] */
return true;
@@ -8380,12 +8388,12 @@ get_rule_expr_paren(Node *node, deparse_context *context,
* get_json_format - Parse back a JsonFormat node
*/
static void
-get_json_format(JsonFormat *format, deparse_context *context)
+get_json_format(JsonFormat *format, StringInfo buf)
{
if (format->format_type == JS_FORMAT_DEFAULT)
return;
- appendStringInfoString(context->buf,
+ appendStringInfoString(buf,
format->format_type == JS_FORMAT_JSONB ?
" FORMAT JSONB" : " FORMAT JSON");
@@ -8395,7 +8403,7 @@ get_json_format(JsonFormat *format, deparse_context *context)
format->encoding == JS_ENC_UTF16 ? "UTF16" :
format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8";
- appendStringInfo(context->buf, " ENCODING %s", encoding);
+ appendStringInfo(buf, " ENCODING %s", encoding);
}
}
@@ -8403,20 +8411,20 @@ get_json_format(JsonFormat *format, deparse_context *context)
* get_json_returning - Parse back a JsonReturning structure
*/
static void
-get_json_returning(JsonReturning *returning, deparse_context *context,
+get_json_returning(JsonReturning *returning, StringInfo buf,
bool json_format_by_default)
{
if (!OidIsValid(returning->typid))
return;
- appendStringInfo(context->buf, " RETURNING %s",
+ appendStringInfo(buf, " RETURNING %s",
format_type_with_typemod(returning->typid,
returning->typmod));
if (!json_format_by_default ||
returning->format->format_type !=
(returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON))
- get_json_format(returning->format, context);
+ get_json_format(returning->format, buf);
}
/* ----------
@@ -9583,10 +9591,14 @@ get_rule_expr(Node *node, deparse_context *context,
JsonValueExpr *jve = (JsonValueExpr *) node;
get_rule_expr((Node *) jve->raw_expr, context, false);
- get_json_format(jve->format, context);
+ get_json_format(jve->format, context->buf);
}
break;
+ case T_JsonConstructorExpr:
+ get_json_constructor((JsonConstructorExpr *) node, context, false);
+ break;
+
case T_List:
{
char *sep;
@@ -9855,17 +9867,89 @@ 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");
+
+ 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_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
@@ -9895,13 +9979,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))
@@ -9937,7 +10022,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);
@@ -9951,6 +10046,9 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
}
}
+ if (options)
+ appendStringInfoString(buf, options);
+
if (aggref->aggfilter != NULL)
{
appendStringInfoString(buf, ") FILTER (WHERE ");
@@ -9960,6 +10058,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
@@ -9979,10 +10087,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];
@@ -10006,16 +10116,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)
{
@@ -10052,6 +10176,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
*
@@ -10292,6 +10425,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/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index 84435420e4..d14b751058 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -763,6 +763,18 @@ JumbleExpr(JumbleState *jstate, Node *node)
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_List:
foreach(temp, (List *) node)
{
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 2843f4b415..1934f19335 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -567,14 +567,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 d8e8715ed1..1933bb8980 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8737,6 +8737,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' },
@@ -8744,10 +8748,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',
@@ -8756,6 +8776,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',
@@ -9628,6 +9661,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',
@@ -9636,10 +9673,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',
@@ -9648,6 +9704,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 56a89ebafb..c830fcf726 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -239,6 +239,7 @@ typedef enum ExprEvalOp
EEOP_GROUPING_FUNC,
EEOP_WINDOW_FUNC,
EEOP_SUBPLAN,
+ EEOP_JSON_CONSTRUCTOR,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -668,6 +669,17 @@ 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;
+ int nargs;
+ } json_constructor;
+
} d;
} ExprEvalStep;
@@ -769,6 +781,8 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
+extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index ec8b71a685..e50b933288 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -109,6 +109,7 @@ extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_
extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding,
int location);
extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format);
+extern Node *makeJsonKeyValue(Node *key, Node *value);
extern JsonEncoding makeJsonEncoding(char *name);
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 59737f1034..05f0b79e82 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -204,6 +204,7 @@ typedef enum NodeTag
T_JsonFormat,
T_JsonReturning,
T_JsonValueExpr,
+ T_JsonConstructorExpr,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -494,6 +495,13 @@ typedef enum NodeTag
T_VacuumRelation,
T_PublicationObjSpec,
T_PublicationTable,
+ T_JsonObjectConstructor,
+ T_JsonArrayConstructor,
+ T_JsonArrayQueryConstructor,
+ T_JsonAggConstructor,
+ T_JsonObjectAgg,
+ T_JsonArrayAgg,
+ T_JsonKeyValue,
T_JsonOutput,
/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 712e56b5f2..0136ae191b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1563,9 +1563,103 @@ typedef struct JsonOutput
{
NodeTag type;
TypeName *typeName; /* RETURNING type name, if specified */
- JsonReturning returning; /* RETURNING FORMAT clause and type Oids */
+ JsonReturning *returning; /* RETURNING FORMAT clause and type Oids */
} JsonOutput;
+/*
+ * JsonKeyValue -
+ * untransformed representation of JSON object key-value pair for
+ * JSON_OBJECT() and JSON_OBJECTAGG()
+ */
+typedef struct JsonKeyValue
+{
+ NodeTag type;
+ Expr *key; /* key expression */
+ JsonValueExpr *value; /* JSON value expression */
+} JsonKeyValue;
+
+/*
+ * JsonObjectConstructor -
+ * untransformed representation of JSON_OBJECT() constructor
+ */
+typedef struct JsonObjectConstructor
+{
+ NodeTag type;
+ List *exprs; /* list of JsonKeyValue pairs */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ bool absent_on_null; /* skip NULL values? */
+ bool unique; /* check key uniqueness? */
+ int location; /* token location, or -1 if unknown */
+} JsonObjectConstructor;
+
+/*
+ * JsonArrayConstructor -
+ * untransformed representation of JSON_ARRAY(element,...) constructor
+ */
+typedef struct JsonArrayConstructor
+{
+ NodeTag type;
+ List *exprs; /* list of JsonValueExpr elements */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ bool absent_on_null; /* skip NULL elements? */
+ int location; /* token location, or -1 if unknown */
+} JsonArrayConstructor;
+
+/*
+ * JsonArrayQueryConstructor -
+ * untransformed representation of JSON_ARRAY(subquery) constructor
+ */
+typedef struct JsonArrayQueryConstructor
+{
+ NodeTag type;
+ Node *query; /* subquery */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ JsonFormat *format; /* FORMAT clause for subquery, if specified */
+ bool absent_on_null; /* skip NULL elements? */
+ int location; /* token location, or -1 if unknown */
+} JsonArrayQueryConstructor;
+
+/*
+ * JsonAggConstructor -
+ * common fields of untransformed representation of
+ * JSON_ARRAYAGG() and JSON_OBJECTAGG()
+ */
+typedef struct JsonAggConstructor
+{
+ NodeTag type;
+ JsonOutput *output; /* RETURNING clause, if any */
+ Node *agg_filter; /* FILTER clause, if any */
+ List *agg_order; /* ORDER BY clause, if any */
+ struct WindowDef *over; /* OVER clause, if any */
+ int location; /* token location, or -1 if unknown */
+} JsonAggConstructor;
+
+/*
+ * JsonObjectAgg -
+ * untransformed representation of JSON_OBJECTAGG()
+ */
+typedef struct JsonObjectAgg
+{
+ NodeTag type;
+ JsonAggConstructor *constructor; /* common fields */
+ JsonKeyValue *arg; /* object key-value pair */
+ bool absent_on_null; /* skip NULL values? */
+ bool unique; /* check key uniqueness? */
+} JsonObjectAgg;
+
+/*
+ * JsonArrayAgg -
+ * untransformed representation of JSON_ARRRAYAGG()
+ */
+typedef struct JsonArrayAgg
+{
+ NodeTag type;
+ JsonAggConstructor *constructor; /* common fields */
+ JsonValueExpr *arg; /* array element expression */
+ bool absent_on_null; /* skip NULL elements? */
+} JsonArrayAgg;
+
+
/*****************************************************************************
* Raw Grammar Output Statements
*****************************************************************************/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 8e3c99bdb5..c48527e998 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1292,6 +1292,31 @@ typedef struct JsonValueExpr
JsonFormat *format; /* FORMAT clause, if specified */
} JsonValueExpr;
+typedef enum JsonConstructorType
+{
+ JSCTOR_JSON_OBJECT = 1,
+ JSCTOR_JSON_ARRAY = 2,
+ JSCTOR_JSON_OBJECTAGG = 3,
+ JSCTOR_JSON_ARRAYAGG = 4
+} JsonConstructorType;
+
+/*
+ * JsonConstructorExpr -
+ * wrapper over FuncExpr/Aggref/WindowFunc for SQL/JSON constructors
+ */
+typedef struct JsonConstructorExpr
+{
+ Expr xpr;
+ JsonConstructorType type; /* constructor type */
+ List *args;
+ Expr *func; /* underlying json[b]_xxx() function call */
+ Expr *coercion; /* coercion to RETURNING type */
+ JsonReturning *returning; /* RETURNING clause */
+ bool absent_on_null; /* ABSENT ON NULL? */
+ bool unique; /* WITH UNIQUE KEYS? (JSON_OBJECT[AGG] only) */
+ int location;
+} JsonConstructorExpr;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f3502b8be4..f44440d4a9 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)
@@ -229,7 +230,12 @@ PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL)
PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index 8a84a0cdb4..63d83b815f 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -20,5 +20,11 @@
extern void escape_json(StringInfo buf, const char *str);
extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
const int *tzp);
+extern bool to_json_is_immutable(Oid typoid);
+extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null,
+ bool unique_keys);
+extern Datum json_build_array_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null);
#endif /* JSON_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 4cbe6edf21..6bcf35dd0a 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;
/*
@@ -412,4 +414,11 @@ extern Datum jsonb_set_element(Jsonb *jb, Datum *path, int path_len,
JsonbValue *newval);
extern Datum jsonb_get_element(Jsonb *jb, Datum *path, int npath,
bool *isnull, bool as_text);
+extern bool to_jsonb_is_immutable(Oid typoid);
+extern Datum jsonb_build_object_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null,
+ bool unique_keys);
+extern Datum jsonb_build_array_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null);
+
#endif /* __JSONB_H__ */
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index dee6b8200d..5ec511fd01 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 a44e07a17a..5e2b606f9b 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/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 4ce6c039b4..15e4016836 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..7dca5a8a30
--- /dev/null
+++ b/src/test/regress/expected/sqljson.out
@@ -0,0 +1,746 @@
+-- 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;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 6d8f524ae9..3ce701a588 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
# ----------
# Another group of parallel tests (JSON related)
# ----------
-test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath
+test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 2b292851e3..63fe114fed 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..aaef2d8aab
--- /dev/null
+++ b/src/test/regress/sql/sqljson.sql
@@ -0,0 +1,282 @@
+-- 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;
--
2.25.1
[text/x-patch] 0003-IS-JSON-predicate-v65.patch (54.5K, ../../[email protected]/4-0003-IS-JSON-predicate-v65.patch)
download | inline diff:
From 892b54ab838d0f4447f90da1d0aa80bde084ad44 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:02:53 -0500
Subject: [PATCH 3/6] IS JSON predicate
---
doc/src/sgml/func.sgml | 251 +++++++++++++++++++++++++-
src/backend/executor/execExpr.c | 13 ++
src/backend/executor/execExprInterp.c | 95 ++++++++++
src/backend/jit/llvm/llvmjit_expr.c | 6 +
src/backend/jit/llvm/llvmjit_types.c | 1 +
src/backend/nodes/copyfuncs.c | 20 ++
src/backend/nodes/equalfuncs.c | 15 ++
src/backend/nodes/makefuncs.c | 19 ++
src/backend/nodes/nodeFuncs.c | 26 +++
src/backend/nodes/outfuncs.c | 14 ++
src/backend/nodes/readfuncs.c | 18 ++
src/backend/parser/gram.y | 63 ++++++-
src/backend/parser/parse_expr.c | 76 ++++++++
src/backend/utils/adt/json.c | 105 +++++++++--
src/backend/utils/adt/jsonfuncs.c | 20 ++
src/backend/utils/adt/ruleutils.c | 35 ++++
src/backend/utils/misc/queryjumble.c | 10 +
src/include/executor/execExpr.h | 8 +
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/primnodes.h | 26 +++
src/include/parser/kwlist.h | 1 +
src/include/utils/json.h | 1 +
src/include/utils/jsonfuncs.h | 3 +
src/test/regress/expected/sqljson.out | 198 ++++++++++++++++++++
src/test/regress/sql/sqljson.sql | 96 ++++++++++
26 files changed, 1106 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3124f87ceb..89ce508922 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17606,7 +17606,16 @@ $.* ? (@ like_regex "^\\d+$")
</listitem>
</itemizedlist>
- <sect3 id="functions-sqljson-producing">
+ <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>
@@ -18366,10 +18375,250 @@ FROM films AS f;
</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>
+ </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-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="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>
+[ PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } [, ...]]</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>
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index a9547aaef1..acd3ea6134 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2513,6 +2513,19 @@ ExecInitExprRec(Expr *node, ExprState *state,
}
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;
+ }
+
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 f2a0821a7a..c0bd955620 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -73,6 +73,7 @@
#include "utils/expandedrecord.h"
#include "utils/json.h"
#include "utils/jsonb.h"
+#include "utils/jsonfuncs.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -480,6 +481,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_WINDOW_FUNC,
&&CASE_EEOP_SUBPLAN,
&&CASE_EEOP_JSON_CONSTRUCTOR,
+ &&CASE_EEOP_IS_JSON,
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
&&CASE_EEOP_AGG_DESERIALIZE,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1799,6 +1801,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_IS_JSON)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalJsonIsPredicate(state, op);
+
+ EEO_NEXT();
+ }
+
EEO_CASE(EEOP_LAST)
{
/* unreachable */
@@ -3909,6 +3919,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);
+ }
+ 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
*
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index d0c26cf58b..02511c6aec 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2354,6 +2354,12 @@ llvm_compile_expr(ExprState *state)
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_LAST:
Assert(false);
break;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 53c75dd9d6..4d7029a27f 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -132,6 +132,7 @@ void *referenced_functions[] =
ExecEvalWholeRowVar,
ExecEvalXmlExpr,
ExecEvalJsonConstructor,
+ ExecEvalJsonIsPredicate,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d89596a74a..ce3102a452 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2490,6 +2490,23 @@ _copyJsonArrayQueryConstructor(const JsonArrayQueryConstructor *from)
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;
+}
+
/* ****************************************************************
* pathnodes.h copy functions
*
@@ -5578,6 +5595,9 @@ copyObjectImpl(const void *from)
case T_JsonArrayAgg:
retval = _copyJsonArrayAgg(from);
break;
+ case T_JsonIsPredicate:
+ retval = _copyJsonIsPredicate(from);
+ break;
/*
* RELATION NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 9cb8495ddf..0fda7680b3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -976,6 +976,18 @@ _equalJsonArrayQueryConstructor(const JsonArrayQueryConstructor *a,
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;
+}
+
/*
* Stuff from pathnodes.h
*/
@@ -3505,6 +3517,9 @@ equal(const void *a, const void *b)
case T_JsonConstructorExpr:
retval = _equalJsonConstructorExpr(a, b);
break;
+ case T_JsonIsPredicate:
+ retval = _equalJsonIsPredicate(a, b);
+ break;
/*
* RELATION NODES
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 7b4f7972e6..b67e7c5297 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -887,3 +887,22 @@ makeJsonKeyValue(Node *key, Node *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 25cf282aab..8716fbd87c 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -260,6 +260,9 @@ exprType(const Node *expr)
case T_JsonConstructorExpr:
type = ((const JsonConstructorExpr *) expr)->returning->typid;
break;
+ case T_JsonIsPredicate:
+ type = BOOLOID;
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -985,6 +988,9 @@ exprCollation(const Node *expr)
coll = InvalidOid;
}
break;
+ case T_JsonIsPredicate:
+ coll = InvalidOid; /* result is always an boolean type */
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
@@ -1211,6 +1217,9 @@ exprSetCollation(Node *expr, Oid collation)
Assert(!OidIsValid(collation)); /* result is always a json[b] type */
}
break;
+ case T_JsonIsPredicate:
+ Assert(!OidIsValid(collation)); /* result is always boolean */
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1663,6 +1672,9 @@ exprLocation(const Node *expr)
case T_JsonConstructorExpr:
loc = ((const JsonConstructorExpr *) expr)->location;
break;
+ case T_JsonIsPredicate:
+ loc = ((const JsonIsPredicate *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2419,6 +2431,8 @@ expression_tree_walker(Node *node,
return true;
}
break;
+ case T_JsonIsPredicate:
+ return walker(((JsonIsPredicate *) node)->expr, context);
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -3414,6 +3428,16 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->coercion, jve->coercion, Expr *);
MUTATE(newnode->returning, jve->returning, JsonReturning *);
+ return (Node *) newnode;
+ }
+ case T_JsonIsPredicate:
+ {
+ JsonIsPredicate *pred = (JsonIsPredicate *) node;
+ JsonIsPredicate *newnode;
+
+ FLATCOPY(newnode, pred, JsonIsPredicate);
+ MUTATE(newnode->expr, pred->expr, Node *);
+
return (Node *) newnode;
}
default:
@@ -4237,6 +4261,8 @@ raw_expression_tree_walker(Node *node,
return true;
}
break;
+ case T_JsonIsPredicate:
+ return walker(((JsonIsPredicate *) node)->expr, context);
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 c25f0bd684..ed5d159f6c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1796,6 +1796,17 @@ _outJsonConstructorExpr(StringInfo str, const JsonConstructorExpr *node)
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);
+}
+
/*****************************************************************************
*
* Stuff from pathnodes.h.
@@ -4594,6 +4605,9 @@ outNode(StringInfo str, const void *obj)
case T_JsonConstructorExpr:
_outJsonConstructorExpr(str, obj);
break;
+ case T_JsonIsPredicate:
+ _outJsonIsPredicate(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e0b3ad1ed2..aaf947b6cb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1454,6 +1454,22 @@ _readJsonConstructorExpr(void)
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.
*
@@ -3047,6 +3063,8 @@ parseNodeString(void)
return_value = _readJsonValueExpr();
else if (MATCH("JSONCTOREXPR", 12))
return_value = _readJsonConstructorExpr();
+ else if (MATCH("JSONISPREDICATE", 15))
+ return_value = _readJsonIsPredicate();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 055df94bcc..9f371dafd9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -658,6 +658,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> json_encoding
json_encoding_clause_opt
+ json_predicate_type_constraint_opt
%type <boolean> json_key_uniqueness_constraint_opt
json_object_constructor_null_clause_opt
@@ -727,7 +728,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG
- KEY KEYS
+ KEY KEYS KEEP
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
@@ -755,9 +756,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
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
+ 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 STRIP_P
SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
@@ -845,13 +846,14 @@ 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 ABSENT UNIQUE
+%nonassoc ABSENT 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
@@ -13937,6 +13939,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
{
/*
@@ -14019,6 +14061,14 @@ 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; }
@@ -16206,6 +16256,7 @@ unreserved_keyword:
| ROWS
| RULE
| SAVEPOINT
+ | SCALAR
| SCHEMA
| SCHEMAS
| SCROLL
@@ -16676,6 +16727,7 @@ bare_label_keyword:
| JSON_ARRAYAGG
| JSON_OBJECT
| JSON_OBJECTAGG
+ | KEEP
| KEY
| KEYS
| LABEL
@@ -16803,6 +16855,7 @@ bare_label_keyword:
| ROWS
| RULE
| SAVEPOINT
+ | SCALAR
| SCHEMA
| SCHEMAS
| SCROLL
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6b93a76bca..0e504096fd 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -85,6 +85,7 @@ 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 *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -332,6 +333,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr);
break;
+ case T_JsonIsPredicate:
+ result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3869,3 +3874,74 @@ transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor)
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);
+}
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index d088fafc56..5edcb8bb60 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -13,6 +13,7 @@
*/
#include "postgres.h"
+#include "access/hash.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
@@ -1655,6 +1656,94 @@ 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)
+{
+ 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)
+ return false; /* invalid json */
+
+ if (check_unique_keys && !state.unique)
+ return false; /* not unique keys */
+
+ return true; /* ok */
+}
+
/*
* SQL function json_typeof(json) -> text
*
@@ -1670,21 +1759,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/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 29664aa6e4..a24d498b06 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -5528,3 +5528,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/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 6db6c008dd..9b2d326e83 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8194,6 +8194,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:
@@ -9599,6 +9600,40 @@ get_rule_expr(Node *node, deparse_context *context,
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_List:
{
char *sep;
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index d14b751058..8315812793 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -775,6 +775,16 @@ JumbleExpr(JumbleState *jstate, Node *node)
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_List:
foreach(temp, (List *) node)
{
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index c830fcf726..a41722ae1e 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -240,6 +240,7 @@ typedef enum ExprEvalOp
EEOP_WINDOW_FUNC,
EEOP_SUBPLAN,
EEOP_JSON_CONSTRUCTOR,
+ EEOP_IS_JSON,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -680,6 +681,12 @@ typedef struct ExprEvalStep
int nargs;
} json_constructor;
+ /* for EEOP_IS_JSON */
+ struct
+ {
+ JsonIsPredicate *pred; /* original expression node */
+ } is_json;
+
} d;
} ExprEvalStep;
@@ -774,6 +781,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);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index e50b933288..380940968b 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -110,6 +110,9 @@ extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding,
int location);
extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format);
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 05f0b79e82..666b45c5da 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -501,6 +501,7 @@ typedef enum NodeTag
T_JsonAggConstructor,
T_JsonObjectAgg,
T_JsonArrayAgg,
+ T_JsonIsPredicate,
T_JsonKeyValue,
T_JsonOutput,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index c48527e998..f4a39653ac 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1317,6 +1317,32 @@ typedef struct JsonConstructorExpr
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;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f44440d4a9..1726d73da6 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -372,6 +372,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)
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index 63d83b815f..bfe5b21591 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -26,5 +26,6 @@ extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls,
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);
#endif /* JSON_H */
diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h
index 865b2ff7c1..cd16b6c0c8 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);
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 7dca5a8a30..27dca7815a 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -744,3 +744,201 @@ CREATE OR REPLACE VIEW public.json_array_subquery_view AS
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/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
index aaef2d8aab..4f3c06dcb3 100644
--- a/src/test/regress/sql/sqljson.sql
+++ b/src/test/regress/sql/sqljson.sql
@@ -280,3 +280,99 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING
\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.1
[text/x-patch] 0004-SQL-JSON-query-functions-v65.patch (197.0K, ../../[email protected]/5-0004-SQL-JSON-query-functions-v65.patch)
download | inline diff:
From b71e51e7cae270d32f3d91d656969dfc8dd9df51 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:11:14 -0500
Subject: [PATCH 4/6] SQL/JSON query functions
---
doc/src/sgml/func.sgml | 516 +++++++++-
src/backend/executor/execExpr.c | 206 +++-
src/backend/executor/execExprInterp.c | 543 ++++++++++
src/backend/jit/llvm/llvmjit_expr.c | 6 +
src/backend/jit/llvm/llvmjit_types.c | 1 +
src/backend/nodes/copyfuncs.c | 150 +++
src/backend/nodes/equalfuncs.c | 127 +++
src/backend/nodes/makefuncs.c | 15 +
src/backend/nodes/nodeFuncs.c | 186 +++-
src/backend/nodes/outfuncs.c | 70 ++
src/backend/nodes/readfuncs.c | 86 ++
src/backend/optimizer/path/costsize.c | 3 +-
src/backend/optimizer/util/clauses.c | 32 +
src/backend/parser/gram.y | 333 +++++-
src/backend/parser/parse_collate.c | 4 +
src/backend/parser/parse_expr.c | 490 ++++++++-
src/backend/parser/parse_target.c | 15 +
src/backend/utils/adt/formatting.c | 45 +-
src/backend/utils/adt/jsonb.c | 62 ++
src/backend/utils/adt/jsonfuncs.c | 50 +-
src/backend/utils/adt/jsonpath.c | 257 +++++
src/backend/utils/adt/jsonpath_exec.c | 350 ++++++-
src/backend/utils/adt/ruleutils.c | 135 +++
src/backend/utils/misc/queryjumble.c | 21 +
src/include/executor/execExpr.h | 54 +
src/include/executor/executor.h | 2 +
src/include/nodes/makefuncs.h | 1 +
src/include/nodes/nodes.h | 7 +
src/include/nodes/parsenodes.h | 59 ++
src/include/nodes/primnodes.h | 109 ++
src/include/parser/kwlist.h | 11 +
src/include/utils/formatting.h | 4 +
src/include/utils/jsonb.h | 3 +
src/include/utils/jsonfuncs.h | 4 +
src/include/utils/jsonpath.h | 33 +
src/test/regress/expected/json_sqljson.out | 15 +
src/test/regress/expected/jsonb_sqljson.out | 1018 +++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/json_sqljson.sql | 11 +
src/test/regress/sql/jsonb_sqljson.sql | 317 ++++++
40 files changed, 5229 insertions(+), 124 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/sql/json_sqljson.sql
create mode 100644 src/test/regress/sql/jsonb_sqljson.sql
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 89ce508922..2333ee9689 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18394,6 +18394,21 @@ FROM films AS f;
<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>
@@ -18423,6 +18438,501 @@ INSERT INTO my_films VALUES (
</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>
@@ -18582,7 +19092,7 @@ FROM
<varlistentry>
<term>
<literal><replaceable>context_item</replaceable>, <replaceable>path_expression</replaceable>
-[ PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } [, ...]]</literal>
+<optional> PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } <optional>, ...</optional></optional></literal>
</term>
<listitem>
<para>
@@ -18611,7 +19121,7 @@ FROM
</listitem>
</itemizedlist>
<para>
- The input clause is common for all SQL/JSON query functions.
+ The input clause is common for all SQL/JSON query functions.
</para>
</listitem>
</varlistentry>
@@ -18625,7 +19135,7 @@ FROM
<variablelist>
<varlistentry>
<term>
- <literal>RETURNING <replaceable class="parameter">data_type</replaceable> [ FORMAT JSON [ ENCODING UTF8 ] ]</literal>
+ <literal>RETURNING <replaceable class="parameter">data_type</replaceable> <optional> FORMAT JSON <optional> ENCODING UTF8 </optional> </optional></literal>
</term>
<listitem>
<para>
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index acd3ea6134..1c364a7f4c 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -47,6 +47,7 @@
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/datum.h"
+#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
@@ -85,6 +86,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 +157,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 +169,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);
}
/*
@@ -2526,6 +2524,112 @@ ExecInitExprRec(Expr *node, ExprState *state,
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->sval);
+ 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 c0bd955620..4c421fd227 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -57,14 +57,18 @@
#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"
@@ -74,8 +78,10 @@
#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"
@@ -482,6 +488,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&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,
@@ -1805,7 +1812,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
{
/* 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();
}
@@ -4523,3 +4536,533 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
*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 02511c6aec..9c8f341d96 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2360,6 +2360,12 @@ llvm_compile_expr(ExprState *state)
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 4d7029a27f..b2bda86889 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -133,6 +133,7 @@ void *referenced_functions[] =
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 ce3102a452..e5c9f403e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2490,6 +2490,90 @@ _copyJsonArrayQueryConstructor(const JsonArrayQueryConstructor *from)
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
*/
@@ -2507,6 +2591,51 @@ _copyJsonIsPredicate(const JsonIsPredicate *from)
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
*
@@ -5598,6 +5727,27 @@ copyObjectImpl(const void *from)
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 0fda7680b3..718884ada6 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -976,6 +976,42 @@ _equalJsonArrayQueryConstructor(const JsonArrayQueryConstructor *a,
return true;
}
+static bool
+_equalJsonFuncExpr(const JsonFuncExpr *a, const JsonFuncExpr *b)
+{
+ COMPARE_SCALAR_FIELD(op);
+ COMPARE_NODE_FIELD(common);
+ COMPARE_NODE_FIELD(output);
+ COMPARE_NODE_FIELD(on_empty);
+ COMPARE_NODE_FIELD(on_error);
+ COMPARE_SCALAR_FIELD(wrapper);
+ COMPARE_SCALAR_FIELD(omit_quotes);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalJsonCommon(const JsonCommon *a, const JsonCommon *b)
+{
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(pathspec);
+ COMPARE_STRING_FIELD(pathname);
+ COMPARE_NODE_FIELD(passing);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalJsonArgument(const JsonArgument *a, const JsonArgument *b)
+{
+ COMPARE_NODE_FIELD(val);
+ COMPARE_STRING_FIELD(name);
+
+ return true;
+}
+
static bool
_equalJsonIsPredicate(const JsonIsPredicate *a,
const JsonIsPredicate *b)
@@ -988,6 +1024,76 @@ _equalJsonIsPredicate(const JsonIsPredicate *a,
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
*/
@@ -3520,6 +3626,18 @@ equal(const void *a, const void *b)
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
@@ -4124,6 +4242,15 @@ equal(const void *a, const void *b)
case T_JsonArrayAgg:
retval = _equalJsonArrayAgg(a, b);
break;
+ case T_JsonFuncExpr:
+ retval = _equalJsonFuncExpr(a, b);
+ break;
+ case T_JsonCommon:
+ retval = _equalJsonCommon(a, b);
+ break;
+ case T_JsonArgument:
+ retval = _equalJsonArgument(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 b67e7c5297..cd6c300e7b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -852,6 +852,21 @@ makeJsonValueExpr(Expr *expr, JsonFormat *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
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 8716fbd87c..6f6a1f4ffc 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -263,6 +263,12 @@ exprType(const Node *expr)
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 */
@@ -498,7 +504,11 @@ exprTypmod(const Node *expr)
case T_JsonValueExpr:
return exprTypmod((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
case T_JsonConstructorExpr:
- return -1; /* ((const JsonConstructorExpr *) expr)->returning->typmod; */
+ 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;
}
@@ -991,6 +1001,21 @@ exprCollation(const Node *expr)
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 */
@@ -1220,6 +1245,21 @@ exprSetCollation(Node *expr, Oid collation)
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;
@@ -1675,6 +1715,15 @@ exprLocation(const Node *expr)
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;
@@ -2433,6 +2482,54 @@ expression_tree_walker(Node *node,
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));
@@ -3430,6 +3527,7 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
+ break;
case T_JsonIsPredicate:
{
JsonIsPredicate *pred = (JsonIsPredicate *) node;
@@ -3440,6 +3538,55 @@ expression_tree_mutator(Node *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));
@@ -4263,6 +4410,43 @@ raw_expression_tree_walker(Node *node,
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 ed5d159f6c..23750d97ab 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1807,6 +1807,64 @@ _outJsonIsPredicate(StringInfo str, const JsonIsPredicate *node)
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.
@@ -4608,6 +4666,18 @@ outNode(StringInfo str, const void *obj)
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 aaf947b6cb..c15e81a362 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1454,6 +1454,84 @@ _readJsonConstructorExpr(void)
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
*/
@@ -3065,6 +3143,14 @@ parseNodeString(void)
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 4d9f3b4bb6..09271694ee 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -4546,7 +4546,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 e1147c431e..e381ae512a 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"
@@ -52,6 +53,7 @@
#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"
@@ -405,6 +407,24 @@ contain_mutable_functions_walker(Node *node, void *context)
/* 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 */
@@ -876,6 +896,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,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9f371dafd9..a940755332 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -280,6 +280,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct GroupClause *groupclause;
struct KeyActions *keyactions;
struct KeyAction *keyaction;
+ JsonBehavior *jsbehavior;
+ struct
+ {
+ JsonBehavior *on_empty;
+ JsonBehavior *on_error;
+ } on_behavior;
+ JsonQuotes js_quotes;
}
%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -639,7 +646,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_representation
json_value_expr
json_func_expr
+ json_value_func_expr
+ json_query_expr
+ json_exists_predicate
+ 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
@@ -651,15 +665,43 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
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
@@ -699,7 +741,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
@@ -710,8 +752,8 @@ 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
@@ -726,7 +768,8 @@ 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 JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG
+ JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG
+ JSON_QUERY JSON_VALUE
KEY KEYS KEEP
@@ -741,7 +784,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
@@ -749,7 +792,7 @@ 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
@@ -759,7 +802,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
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 STRIP_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
@@ -767,7 +810,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
@@ -846,7 +889,8 @@ 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 ABSENT UNIQUE JSON
+%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 '+' '-'
@@ -15345,6 +15389,80 @@ opt_asymmetric: ASYMMETRIC
/* SQL/JSON support */
json_func_expr:
json_value_constructor
+ | json_value_func_expr
+ | json_query_expr
+ | json_exists_predicate
+ ;
+
+
+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:
@@ -15383,6 +15501,153 @@ 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 must 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
{
@@ -15395,6 +15660,35 @@ json_output_clause_opt:
| /* 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
@@ -16065,6 +16359,7 @@ unreserved_keyword:
| COMMIT
| COMMITTED
| COMPRESSION
+ | CONDITIONAL
| CONFIGURATION
| CONFLICT
| CONNECTION
@@ -16101,10 +16396,12 @@ unreserved_keyword:
| DOUBLE_P
| DROP
| EACH
+ | EMPTY_P
| ENABLE_P
| ENCODING
| ENCRYPTED
| ENUM_P
+ | ERROR_P
| ESCAPE
| EVENT
| EXCLUDE
@@ -16154,6 +16451,7 @@ unreserved_keyword:
| INVOKER
| ISOLATION
| JSON
+ | KEEP
| KEY
| KEYS
| LABEL
@@ -16198,6 +16496,7 @@ unreserved_keyword:
| OFF
| OIDS
| OLD
+ | OMIT
| OPERATOR
| OPTION
| OPTIONS
@@ -16227,6 +16526,7 @@ unreserved_keyword:
| PROGRAM
| PUBLICATION
| QUOTE
+ | QUOTES
| RANGE
| READ
| REASSIGN
@@ -16308,6 +16608,7 @@ unreserved_keyword:
| UESCAPE
| UNBOUNDED
| UNCOMMITTED
+ | UNCONDITIONAL
| UNENCRYPTED
| UNKNOWN
| UNLISTEN
@@ -16367,8 +16668,11 @@ col_name_keyword:
| INTERVAL
| JSON_ARRAY
| JSON_ARRAYAGG
+ | JSON_EXISTS
| JSON_OBJECT
| JSON_OBJECTAGG
+ | JSON_QUERY
+ | JSON_VALUE
| LEAST
| NATIONAL
| NCHAR
@@ -16436,6 +16740,7 @@ type_func_name_keyword:
| OVERLAPS
| RIGHT
| SIMILAR
+ | STRING
| TABLESAMPLE
| VERBOSE
;
@@ -16600,6 +16905,7 @@ bare_label_keyword:
| COMMITTED
| COMPRESSION
| CONCURRENTLY
+ | CONDITIONAL
| CONFIGURATION
| CONFLICT
| CONNECTION
@@ -16652,11 +16958,13 @@ bare_label_keyword:
| DROP
| EACH
| ELSE
+ | EMPTY_P
| ENABLE_P
| ENCODING
| ENCRYPTED
| END_P
| ENUM_P
+ | ERROR_P
| ESCAPE
| EVENT
| EXCLUDE
@@ -16725,8 +17033,11 @@ bare_label_keyword:
| JSON
| JSON_ARRAY
| JSON_ARRAYAGG
+ | JSON_EXISTS
| JSON_OBJECT
| JSON_OBJECTAGG
+ | JSON_QUERY
+ | JSON_VALUE
| KEEP
| KEY
| KEYS
@@ -16786,6 +17097,7 @@ bare_label_keyword:
| OFF
| OIDS
| OLD
+ | OMIT
| ONLY
| OPERATOR
| OPTION
@@ -16822,6 +17134,7 @@ bare_label_keyword:
| PROGRAM
| PUBLICATION
| QUOTE
+ | QUOTES
| RANGE
| READ
| REAL
@@ -16890,6 +17203,7 @@ bare_label_keyword:
| STORAGE
| STORED
| STRICT_P
+ | STRING
| STRIP_P
| SUBSCRIPTION
| SUBSTRING
@@ -16923,6 +17237,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 6c793b72ec..2e549e7b39 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 0e504096fd..7709a6c665 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -86,6 +86,8 @@ static Node *transformJsonArrayQueryConstructor(ParseState *pstate,
static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg);
static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg);
static Node *transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *p);
+static Node *transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *p);
+static Node *transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve);
static Node *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -337,6 +339,14 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr);
break;
+ case T_JsonFuncExpr:
+ result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr);
+ break;
+
+ case T_JsonValueExpr:
+ result = transformJsonValueExpr(pstate, (JsonValueExpr *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3214,8 +3224,8 @@ makeCaseTestExpr(Node *expr)
* default format otherwise.
*/
static Node *
-transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
- JsonFormatType default_format)
+transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
+ JsonFormatType default_format, bool isarg)
{
Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr);
Node *rawexpr;
@@ -3234,6 +3244,8 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
get_type_category_preferred(exprtype, &typcategory, &typispreferred);
+ rawexpr = expr;
+
if (ve->format->format_type != JS_FORMAT_DEFAULT)
{
if (ve->format->encoding != JS_ENC_DEFAULT && exprtype != BYTEAOID)
@@ -3252,12 +3264,44 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
else
format = ve->format->format_type;
}
+ 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)
+ if (format == JS_FORMAT_DEFAULT)
+ expr = rawexpr;
+ else
{
Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
Node *orig = makeCaseTestExpr(expr);
@@ -3265,7 +3309,7 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
expr = orig;
- if (exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+ if (!isarg && exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg(ve->format->format_type == JS_FORMAT_DEFAULT ?
@@ -3316,6 +3360,24 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *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);
+}
+
+/*
+ * Transform JSON value expression using unspecified format by default.
+ */
+static Node *
+transformJsonValueExprDefault(ParseState *pstate, JsonValueExpr *jve)
+{
+ return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false);
+}
+
/*
* Checks specified output format for its applicability to the target type.
*/
@@ -3576,8 +3638,7 @@ transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor)
{
JsonKeyValue *kv = castNode(JsonKeyValue, lfirst(lc));
Node *key = transformExprRecurse(pstate, (Node *) kv->key);
- Node *val = transformJsonValueExpr(pstate, kv->value,
- JS_FORMAT_DEFAULT);
+ Node *val = transformJsonValueExprDefault(pstate, kv->value);
args = lappend(args, key);
args = lappend(args, val);
@@ -3755,7 +3816,7 @@ transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg)
Oid aggtype;
key = transformExprRecurse(pstate, (Node *) agg->arg->key);
- val = transformJsonValueExpr(pstate, agg->arg->value, JS_FORMAT_DEFAULT);
+ val = transformJsonValueExprDefault(pstate, agg->arg->value);
args = list_make2(key, val);
returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
@@ -3813,7 +3874,7 @@ transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg)
const char *aggfnname;
Oid aggtype;
- arg = transformJsonValueExpr(pstate, agg->arg, JS_FORMAT_DEFAULT);
+ arg = transformJsonValueExprDefault(pstate, agg->arg);
returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
list_make1(arg));
@@ -3861,8 +3922,7 @@ transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor)
foreach(lc, ctor->exprs)
{
JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc));
- Node *val = transformJsonValueExpr(pstate, jsval,
- JS_FORMAT_DEFAULT);
+ Node *val = transformJsonValueExprDefault(pstate, jsval);
args = lappend(args, val);
}
@@ -3945,3 +4005,413 @@ transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred)
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);
+
+ 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_type == JS_FORMAT_DEFAULT)
+ is_jsonb = exprType(context_item) == JSONBOID;
+ else
+ is_jsonb = ret->format->format_type == 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_type = 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_type = 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;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 204d285773..ef1eda6532 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1970,6 +1970,21 @@ FigureColnameInternal(Node *node, char **name)
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/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index ed698f788d..ac74333be5 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -1023,11 +1023,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
* ----------
@@ -6672,3 +6667,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/jsonb.c b/src/backend/utils/adt/jsonb.c
index a103cbc7c6..d383cbdfed 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2227,3 +2227,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/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index a24d498b06..a682d9c973 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2658,11 +2658,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);
@@ -3134,6 +3134,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)
{
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 9be4e305ff..ca1cfe3d36 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->sval, 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 eff3734b6a..7811fa31e0 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 9b2d326e83..a79d238735 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -496,6 +496,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 ")
@@ -8101,6 +8103,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_WindowFunc:
case T_FuncExpr:
case T_JsonConstructorExpr:
+ case T_JsonExpr:
/* function-like: name(..) or name[..] */
return true;
@@ -8219,6 +8222,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_GroupingFunc: /* own parentheses */
case T_WindowFunc: /* own parentheses */
case T_CaseExpr: /* other separators */
+ case T_JsonExpr: /* own parentheses */
return true;
default:
return false;
@@ -8385,6 +8389,19 @@ get_rule_expr_paren(Node *node, deparse_context *context,
appendStringInfoChar(context->buf, ')');
}
+
+/*
+ * 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
*/
@@ -8428,6 +8445,66 @@ get_json_returning(JsonReturning *returning, StringInfo buf,
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
*
@@ -9587,6 +9664,7 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+
case T_JsonValueExpr:
{
JsonValueExpr *jve = (JsonValueExpr *) node;
@@ -9634,6 +9712,62 @@ get_rule_expr(Node *node, deparse_context *context,
}
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))->sval);
+ }
+ }
+
+ 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;
@@ -9757,6 +9891,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:
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index 8315812793..7120836c70 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -785,6 +785,27 @@ JumbleExpr(JumbleState *jstate, Node *node)
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)->sval);
+ }
+ 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/executor/execExpr.h b/src/include/executor/execExpr.h
index a41722ae1e..240d07982a 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 */
@@ -241,6 +242,7 @@ typedef enum ExprEvalOp
EEOP_SUBPLAN,
EEOP_JSON_CONSTRUCTOR,
EEOP_IS_JSON,
+ EEOP_JSONEXPR,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -687,6 +689,50 @@ typedef struct ExprEvalStep
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;
@@ -791,6 +837,14 @@ 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 82925b4b63..873772f188 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -265,6 +265,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 380940968b..872f2f0828 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -109,6 +109,7 @@ extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_
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,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 666b45c5da..824e1fca29 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -205,6 +205,9 @@ typedef enum NodeTag
T_JsonReturning,
T_JsonValueExpr,
T_JsonConstructorExpr,
+ T_JsonExpr,
+ T_JsonCoercion,
+ T_JsonItemCoercions,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -501,8 +504,12 @@ typedef enum NodeTag
T_JsonAggConstructor,
T_JsonObjectAgg,
T_JsonArrayAgg,
+ T_JsonFuncExpr,
T_JsonIsPredicate,
+ T_JsonCommon,
+ T_JsonArgument,
T_JsonKeyValue,
+ T_JsonBehavior,
T_JsonOutput,
/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0136ae191b..0a9c40388f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1555,6 +1555,23 @@ typedef struct 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])
@@ -1566,6 +1583,48 @@ typedef struct JsonOutput
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
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f4a39653ac..e4adb2e3f9 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1233,6 +1233,17 @@ 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
@@ -1256,6 +1267,37 @@ typedef enum JsonFormatType
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
@@ -1343,6 +1385,73 @@ typedef struct JsonIsPredicate
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 1726d73da6..69590905c1 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -93,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)
@@ -147,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)
@@ -232,8 +235,12 @@ PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_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_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)
@@ -297,6 +304,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)
@@ -338,6 +346,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)
@@ -408,6 +417,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)
@@ -442,6 +452,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 851e787bfd..0a22af80a2 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/jsonb.h b/src/include/utils/jsonb.h
index 6bcf35dd0a..3fdff445cf 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -407,6 +407,9 @@ 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);
diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h
index cd16b6c0c8..62dc3d88a4 100644
--- a/src/include/utils/jsonfuncs.h
+++ b/src/include/utils/jsonfuncs.h
@@ -58,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 cd0b5d5b61..98a61d7f72 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/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_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
new file mode 100644
index 0000000000..1126d7caf5
--- /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 must 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 must 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 must 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 must 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/parallel_schedule b/src/test/regress/parallel_schedule
index 3ce701a588..b8cea3a5f2 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,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 sqljson
+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_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;
--
2.25.1
[text/x-patch] 0005-SQL-JSON-functions-for-json-type-v65.patch (58.5K, ../../[email protected]/6-0005-SQL-JSON-functions-for-json-type-v65.patch)
download | inline diff:
From f91dd03e18bb0ef3bfc13c87d2a473226063a45a Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Thu, 3 Mar 2022 13:15:13 -0500
Subject: [PATCH 5/6] SQL JSON functions for json type
---
doc/src/sgml/func.sgml | 325 +++++++++++++++++-
doc/src/sgml/keywords/sql2016-02-reserved.txt | 3 +
src/backend/executor/execExpr.c | 45 +++
src/backend/executor/execExprInterp.c | 42 ++-
src/backend/nodes/copyfuncs.c | 53 +++
src/backend/nodes/equalfuncs.c | 38 ++
src/backend/nodes/nodeFuncs.c | 14 +
src/backend/parser/gram.y | 56 ++-
src/backend/parser/parse_expr.c | 152 +++++++-
src/backend/parser/parse_target.c | 9 +
src/backend/utils/adt/format_type.c | 4 +
src/backend/utils/adt/json.c | 51 ++-
src/backend/utils/adt/jsonb.c | 64 ++--
src/backend/utils/adt/ruleutils.c | 13 +-
src/include/executor/execExpr.h | 5 +
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 35 ++
src/include/nodes/primnodes.h | 5 +-
src/include/parser/kwlist.h | 4 +-
src/include/utils/json.h | 21 +-
src/include/utils/jsonb.h | 21 ++
src/test/regress/expected/sqljson.out | 267 ++++++++++++++
src/test/regress/sql/sqljson.sql | 57 +++
23 files changed, 1203 insertions(+), 84 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2333ee9689..891d237de1 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17621,11 +17621,21 @@ $.* ? (@ like_regex "^\\d+$")
<para>
<productname>PostgreSQL</productname> provides several functions
that generate JSON data. Taking values of SQL types as input, these
- functions construct JSON objects or JSON arrays represented as
- SQL character or binary strings.
+ 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>
@@ -17648,6 +17658,219 @@ $.* ? (@ like_regex "^\\d+$")
</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>
@@ -19080,6 +19303,104 @@ FROM
</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">
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 1c364a7f4c..d4d3850ec7 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -47,6 +47,8 @@
#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"
@@ -2460,6 +2462,12 @@ ExecInitExprRec(Expr *node, ExprState *state,
{
ExecInitExprRec(ctor->func, state, resv, resnull);
}
+ else if ((ctor->type == JSCTOR_JSON_PARSE && !ctor->unique) ||
+ ctor->type == JSCTOR_JSON_SERIALIZE)
+ {
+ /* Use the value of the first argument as a result */
+ ExecInitExprRec(linitial(args), state, resv, resnull);
+ }
else
{
scratch.opcode = EEOP_JSON_CONSTRUCTOR;
@@ -2492,6 +2500,43 @@ ExecInitExprRec(Expr *node, ExprState *state,
argno++;
}
+ /* prepare type cache for datum_to_json[b]() */
+ if (ctor->type == JSCTOR_JSON_SCALAR)
+ {
+ bool is_jsonb =
+ ctor->returning->format->format_type == JS_FORMAT_JSONB;
+
+ 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);
}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 4c421fd227..9f626761b1 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3982,7 +3982,7 @@ ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op)
* JSON text validation.
*/
if (res && (pred->unique_keys || exprtype == TEXTOID))
- res = json_validate(json, pred->unique_keys);
+ res = json_validate(json, pred->unique_keys, false);
}
else if (exprtype == JSONBOID)
{
@@ -4527,6 +4527,46 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
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;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e5c9f403e6..b02cf1aeba 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2344,6 +2344,50 @@ _copyJsonValueExpr(const JsonValueExpr *from)
return newnode;
}
+/*
+ * _copyJsonParseExpr
+ */
+static JsonParseExpr *
+_copyJsonParseExpr(const JsonParseExpr *from)
+{
+ JsonParseExpr *newnode = makeNode(JsonParseExpr);
+
+ COPY_NODE_FIELD(expr);
+ COPY_SCALAR_FIELD(unique_keys);
+ COPY_LOCATION_FIELD(location);
+
+ 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
*/
@@ -5697,6 +5741,15 @@ copyObjectImpl(const void *from)
case T_JsonValueExpr:
retval = _copyJsonValueExpr(from);
break;
+ case T_JsonParseExpr:
+ retval = _copyJsonParseExpr(from);
+ break;
+ case T_JsonScalarExpr:
+ retval = _copyJsonScalarExpr(from);
+ break;
+ case T_JsonSerializeExpr:
+ retval = _copyJsonSerializeExpr(from);
+ break;
case T_JsonKeyValue:
retval = _copyJsonKeyValue(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 718884ada6..a22a808f25 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -871,6 +871,35 @@ _equalJsonValueExpr(const JsonValueExpr *a, const JsonValueExpr *b)
return true;
}
+static bool
+_equalJsonParseExpr(const JsonParseExpr *a, const JsonParseExpr *b)
+{
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_SCALAR_FIELD(unique_keys);
+ COMPARE_LOCATION_FIELD(location);
+
+ 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)
{
@@ -3620,6 +3649,15 @@ equal(const void *a, const void *b)
case T_JsonValueExpr:
retval = _equalJsonValueExpr(a, b);
break;
+ case T_JsonParseExpr:
+ retval = _equalJsonParseExpr(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;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 6f6a1f4ffc..4b9001f4b3 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -4310,6 +4310,20 @@ raw_expression_tree_walker(Node *node,
return true;
}
break;
+ case T_JsonParseExpr:
+ return walker(((JsonParseExpr *) node)->expr, context);
+ case T_JsonScalarExpr:
+ return walker(((JsonScalarExpr *) node)->expr, context);
+ case T_JsonSerializeExpr:
+ {
+ JsonSerializeExpr *jse = (JsonSerializeExpr *) node;
+
+ if (walker(jse->expr, context))
+ return true;
+ if (walker(jse->output, context))
+ return true;
+ }
+ break;
case T_JsonConstructorExpr:
{
JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a940755332..f610e17aba 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -562,7 +562,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
@@ -649,6 +649,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
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
@@ -769,7 +772,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG
- JSON_QUERY JSON_VALUE
+ JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_VALUE
KEY KEYS KEEP
@@ -13141,6 +13144,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
@@ -13159,6 +13163,7 @@ ConstTypename:
| ConstBit { $$ = $1; }
| ConstCharacter { $$ = $1; }
| ConstDatetime { $$ = $1; }
+ | JsonType { $$ = $1; }
;
/*
@@ -13527,6 +13532,13 @@ interval_second:
}
;
+JsonType:
+ JSON
+ {
+ $$ = SystemTypeName("json");
+ $$->location = @1;
+ }
+ ;
/*****************************************************************************
*
@@ -15392,8 +15404,42 @@ json_func_expr:
| 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 ')'
+ {
+ JsonParseExpr *n = makeNode(JsonParseExpr);
+ n->expr = (JsonValueExpr *) $3;
+ n->unique_keys = $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_scalar_expr:
+ JSON_SCALAR '(' a_expr ')'
+ {
+ JsonScalarExpr *n = makeNode(JsonScalarExpr);
+ n->expr = (Expr *) $3;
+ 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 '('
@@ -16450,7 +16496,6 @@ unreserved_keyword:
| INSTEAD
| INVOKER
| ISOLATION
- | JSON
| KEEP
| KEY
| KEYS
@@ -16666,12 +16711,15 @@ 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
@@ -17037,6 +17085,8 @@ bare_label_keyword:
| JSON_OBJECT
| JSON_OBJECTAGG
| JSON_QUERY
+ | JSON_SCALAR
+ | JSON_SERIALIZE
| JSON_VALUE
| KEEP
| KEY
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 7709a6c665..bbd80e5c6e 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -88,6 +88,10 @@ 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,
@@ -347,6 +351,18 @@ transformExprRecurse(ParseState *pstate, Node *expr)
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));
@@ -3225,7 +3241,8 @@ makeCaseTestExpr(Node *expr)
*/
static Node *
transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
- JsonFormatType default_format, bool isarg)
+ JsonFormatType default_format, bool isarg,
+ Oid targettype)
{
Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr);
Node *rawexpr;
@@ -3299,17 +3316,17 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
else
format = default_format;
- if (format == JS_FORMAT_DEFAULT)
+ if (format == JS_FORMAT_DEFAULT &&
+ (!OidIsValid(targettype) || exprtype == targettype))
expr = rawexpr;
else
{
- Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
Node *orig = makeCaseTestExpr(expr);
Node *coerced;
+ bool cast_is_needed = OidIsValid(targettype);
- expr = orig;
-
- if (!isarg && exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+ if (!isarg && !cast_is_needed &&
+ exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg(ve->format->format_type == JS_FORMAT_DEFAULT ?
@@ -3318,6 +3335,8 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
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)
{
@@ -3325,6 +3344,9 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
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,
@@ -3335,11 +3357,21 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
if (!coerced)
{
/* If coercion failed, use to_json()/to_jsonb() functions. */
- Oid fnoid = targettype == JSONOID ? F_TO_JSON : F_TO_JSONB;
- FuncExpr *fexpr = makeFuncExpr(fnoid, targettype,
- list_make1(expr),
- InvalidOid, InvalidOid,
- COERCE_EXPLICIT_CALL);
+ FuncExpr *fexpr;
+ Oid fnoid;
+
+ 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;
@@ -3366,7 +3398,8 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
static Node *
transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve)
{
- return transformJsonValueExprExt(pstate, jve, JS_FORMAT_JSON, false);
+ return transformJsonValueExprExt(pstate, jve, JS_FORMAT_JSON, false,
+ InvalidOid);
}
/*
@@ -3375,7 +3408,8 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve)
static Node *
transformJsonValueExprDefault(ParseState *pstate, JsonValueExpr *jve)
{
- return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false);
+ return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false,
+ InvalidOid);
}
/*
@@ -4022,7 +4056,7 @@ transformJsonPassingArgs(ParseState *pstate, JsonFormatType format, List *args,
{
JsonArgument *arg = castNode(JsonArgument, lfirst(lc));
Node *expr = transformJsonValueExprExt(pstate, arg->val,
- format, true);
+ format, true, InvalidOid);
assign_expr_collations(pstate, expr);
@@ -4415,3 +4449,93 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
return (Node *) jsexpr;
}
+
+/*
+ * Transform a JSON() expression.
+ */
+static Node *
+transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr)
+{
+ JsonReturning *returning = makeNode(JsonReturning);
+ Node *arg;
+
+ returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1);
+ returning->typid = JSONOID;
+ returning->typmod = -1;
+
+ 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)
+{
+ JsonReturning *returning = makeNode(JsonReturning);
+ Node *arg = transformExprRecurse(pstate, (Node *) jsexpr->expr);
+
+ returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1);
+ returning->typid = JSONOID;
+ returning->typmod = -1;
+
+ 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 ef1eda6532..829c0f9497 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1957,6 +1957,15 @@ 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;
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 2918fdbfb6..060fd7e183 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -294,6 +294,10 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
else
buf = pstrdup("character varying");
break;
+
+ case JSONOID:
+ buf = pstrdup("json");
+ break;
}
if (buf == NULL)
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 5edcb8bb60..492796eb83 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -30,21 +30,6 @@
#include "utils/lsyscache.h"
#include "utils/typcache.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;
-
/* Common context for key uniqueness check */
typedef struct HTAB *JsonUniqueCheckState; /* hash table for key names */
@@ -99,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);
@@ -180,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)
@@ -762,6 +744,16 @@ 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)
{
@@ -802,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;
@@ -814,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));
}
/*
@@ -1712,7 +1699,7 @@ json_unique_object_field_start(void *_state, char *field, bool isnull)
/* Validate JSON text and additionally check key uniqueness */
bool
-json_validate(text *json, bool check_unique_keys)
+json_validate(text *json, bool check_unique_keys, bool throw_error)
{
JsonLexContext *lex = makeJsonLexContext(json, check_unique_keys);
JsonSemAction uniqueSemAction = {0};
@@ -1736,10 +1723,22 @@ json_validate(text *json, bool check_unique_keys)
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 */
}
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index d383cbdfed..2043f2e74a 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -34,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;
@@ -62,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);
@@ -71,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);
@@ -99,7 +77,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);
}
/*
@@ -123,7 +101,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);
}
/*
@@ -164,6 +142,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.
*/
@@ -254,7 +240,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;
@@ -264,6 +250,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;
@@ -298,6 +286,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
@@ -620,7 +609,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)
@@ -1127,6 +1116,18 @@ 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)
{
@@ -1168,7 +1169,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;
@@ -1180,11 +1180,7 @@ 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));
}
Datum
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a79d238735..2f3f59231a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10056,7 +10056,9 @@ get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf)
if (ctor->unique)
appendStringInfoString(buf, " WITH UNIQUE KEYS");
- get_json_returning(ctor->returning, buf, true);
+ if (ctor->type != JSCTOR_JSON_PARSE &&
+ ctor->type != JSCTOR_JSON_SCALAR)
+ get_json_returning(ctor->returning, buf, true);
}
static void
@@ -10070,6 +10072,15 @@ get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context,
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;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 240d07982a..9ce8df17e5 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -680,6 +680,11 @@ typedef struct ExprEvalStep
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;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 824e1fca29..37deb134b9 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -204,6 +204,9 @@ typedef enum NodeTag
T_JsonFormat,
T_JsonReturning,
T_JsonValueExpr,
+ T_JsonParseExpr,
+ T_JsonScalarExpr,
+ T_JsonSerializeExpr,
T_JsonConstructorExpr,
T_JsonExpr,
T_JsonCoercion,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0a9c40388f..fb8e5637f7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1637,6 +1637,41 @@ typedef struct JsonKeyValue
JsonValueExpr *value; /* JSON value expression */
} JsonKeyValue;
+/*
+ * JsonParseExpr -
+ * untransformed representation of JSON()
+ */
+typedef struct JsonParseExpr
+{
+ NodeTag type;
+ JsonValueExpr *expr; /* string expression */
+ 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 */
+ 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
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index e4adb2e3f9..cc28c20c0e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1339,7 +1339,10 @@ typedef enum JsonConstructorType
JSCTOR_JSON_OBJECT = 1,
JSCTOR_JSON_ARRAY = 2,
JSCTOR_JSON_OBJECTAGG = 3,
- JSCTOR_JSON_ARRAYAGG = 4
+ JSCTOR_JSON_ARRAYAGG = 4,
+ JSCTOR_JSON_SCALAR = 5,
+ JSCTOR_JSON_SERIALIZE = 6,
+ JSCTOR_JSON_PARSE = 7
} JsonConstructorType;
/*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 69590905c1..ab3d8e0b52 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -232,13 +232,15 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL)
PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
-PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json", JSON, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_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)
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index bfe5b21591..da4a9257b3 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -16,16 +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);
+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 3fdff445cf..bae466b523 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -376,6 +376,22 @@ 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;
/* Support functions */
extern uint32 getJsonbOffset(const JsonbContainer *jc, int index);
@@ -403,6 +419,7 @@ 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,
@@ -418,6 +435,10 @@ extern Datum jsonb_set_element(Jsonb *jb, Datum *path, int path_len,
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);
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 27dca7815a..11f5eb2d2c 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -1,3 +1,270 @@
+-- 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)
+
+-- JSON_SCALAR()
+SELECT JSON_SCALAR();
+ERROR: syntax error at or near ")"
+LINE 1: SELECT JSON_SCALAR();
+ ^
+SELECT JSON_SCALAR(NULL);
+ json_scalar
+-------------
+
+(1 row)
+
+SELECT JSON_SCALAR(NULL::int);
+ json_scalar
+-------------
+
+(1 row)
+
+SELECT JSON_SCALAR(123);
+ json_scalar
+-------------
+ 123
+(1 row)
+
+SELECT JSON_SCALAR(123.45);
+ json_scalar
+-------------
+ 123.45
+(1 row)
+
+SELECT JSON_SCALAR(123.45::numeric);
+ json_scalar
+-------------
+ 123.45
+(1 row)
+
+SELECT JSON_SCALAR(true);
+ json_scalar
+-------------
+ true
+(1 row)
+
+SELECT JSON_SCALAR(false);
+ json_scalar
+-------------
+ false
+(1 row)
+
+SELECT JSON_SCALAR(' 123.45');
+ json_scalar
+-------------
+ " 123.45"
+(1 row)
+
+SELECT JSON_SCALAR('2020-06-07'::date);
+ json_scalar
+--------------
+ "2020-06-07"
+(1 row)
+
+SELECT JSON_SCALAR('2020-06-07 01:02:03'::timestamp);
+ json_scalar
+-----------------------
+ "2020-06-07T01:02:03"
+(1 row)
+
+SELECT JSON_SCALAR('{}'::json);
+ json_scalar
+-------------
+ {}
+(1 row)
+
+SELECT JSON_SCALAR('{}'::jsonb);
+ json_scalar
+-------------
+ {}
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123);
+ QUERY PLAN
+----------------------------
+ Result
+ Output: JSON_SCALAR(123)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123');
+ QUERY PLAN
+------------------------------------
+ Result
+ Output: JSON_SCALAR('123'::text)
+(2 rows)
+
+-- JSON_SERIALIZE()
+SELECT JSON_SERIALIZE();
+ERROR: syntax error at or near ")"
+LINE 1: SELECT JSON_SERIALIZE();
+ ^
+SELECT JSON_SERIALIZE(NULL);
+ json_serialize
+----------------
+
+(1 row)
+
+SELECT JSON_SERIALIZE(JSON('{ "a" : 1 } '));
+ json_serialize
+----------------
+ { "a" : 1 }
+(1 row)
+
+SELECT JSON_SERIALIZE('{ "a" : 1 } ');
+ json_serialize
+----------------
+ { "a" : 1 }
+(1 row)
+
+SELECT JSON_SERIALIZE('1');
+ json_serialize
+----------------
+ 1
+(1 row)
+
+SELECT JSON_SERIALIZE('1' FORMAT JSON);
+ json_serialize
+----------------
+ 1
+(1 row)
+
+SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING bytea);
+ json_serialize
+----------------------------
+ \x7b20226122203a2031207d20
+(1 row)
+
+SELECT 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
diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
index 4f3c06dcb3..98bd93c110 100644
--- a/src/test/regress/sql/sqljson.sql
+++ b/src/test/regress/sql/sqljson.sql
@@ -1,3 +1,60 @@
+-- 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);
+
+
+-- JSON_SCALAR()
+SELECT JSON_SCALAR();
+SELECT JSON_SCALAR(NULL);
+SELECT JSON_SCALAR(NULL::int);
+SELECT JSON_SCALAR(123);
+SELECT JSON_SCALAR(123.45);
+SELECT JSON_SCALAR(123.45::numeric);
+SELECT JSON_SCALAR(true);
+SELECT JSON_SCALAR(false);
+SELECT JSON_SCALAR(' 123.45');
+SELECT JSON_SCALAR('2020-06-07'::date);
+SELECT JSON_SCALAR('2020-06-07 01:02:03'::timestamp);
+SELECT JSON_SCALAR('{}'::json);
+SELECT JSON_SCALAR('{}'::jsonb);
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123');
+
+-- JSON_SERIALIZE()
+SELECT JSON_SERIALIZE();
+SELECT JSON_SERIALIZE(NULL);
+SELECT JSON_SERIALIZE(JSON('{ "a" : 1 } '));
+SELECT JSON_SERIALIZE('{ "a" : 1 } ');
+SELECT JSON_SERIALIZE('1');
+SELECT JSON_SERIALIZE('1' FORMAT JSON);
+SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING bytea);
+SELECT 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);
--
2.25.1
[text/x-patch] 0006-RETURNING-clause-for-JSON-and-JSON_SCALAR-v65.patch (10.5K, ../../[email protected]/7-0006-RETURNING-clause-for-JSON-and-JSON_SCALAR-v65.patch)
download | inline diff:
From f136a35da0b60311d6ee877a83e1f111e66c07f3 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <[email protected]>
Date: Sat, 5 Mar 2022 08:07:15 -0500
Subject: [PATCH 6/6] RETURNING clause for JSON() and JSON_SCALAR()
---
src/backend/nodes/copyfuncs.c | 2 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/nodeFuncs.c | 20 +++++++++-
src/backend/parser/gram.y | 7 +++-
src/backend/parser/parse_expr.c | 46 ++++++++++++++++-----
src/backend/utils/adt/ruleutils.c | 5 ++-
src/include/nodes/parsenodes.h | 2 +
src/test/regress/expected/sqljson.out | 57 +++++++++++++++++++++++++++
src/test/regress/sql/sqljson.sql | 10 +++++
9 files changed, 135 insertions(+), 16 deletions(-)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b02cf1aeba..31b488733a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2353,6 +2353,7 @@ _copyJsonParseExpr(const JsonParseExpr *from)
JsonParseExpr *newnode = makeNode(JsonParseExpr);
COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(output);
COPY_SCALAR_FIELD(unique_keys);
COPY_LOCATION_FIELD(location);
@@ -2368,6 +2369,7 @@ _copyJsonScalarExpr(const JsonScalarExpr *from)
JsonScalarExpr *newnode = makeNode(JsonScalarExpr);
COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(output);
COPY_LOCATION_FIELD(location);
return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a22a808f25..e6cb748a18 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -875,6 +875,7 @@ static bool
_equalJsonParseExpr(const JsonParseExpr *a, const JsonParseExpr *b)
{
COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(output);
COMPARE_SCALAR_FIELD(unique_keys);
COMPARE_LOCATION_FIELD(location);
@@ -885,6 +886,7 @@ static bool
_equalJsonScalarExpr(const JsonScalarExpr *a, const JsonScalarExpr *b)
{
COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(output);
COMPARE_LOCATION_FIELD(location);
return true;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 4b9001f4b3..2fb2be2541 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -4311,9 +4311,25 @@ raw_expression_tree_walker(Node *node,
}
break;
case T_JsonParseExpr:
- return walker(((JsonParseExpr *) node)->expr, context);
+ {
+ JsonParseExpr *jpe = (JsonParseExpr *) node;
+
+ if (walker(jpe->expr, context))
+ return true;
+ if (walker(jpe->output, context))
+ return true;
+ }
+ break;
case T_JsonScalarExpr:
- return walker(((JsonScalarExpr *) node)->expr, context);
+ {
+ JsonScalarExpr *jse = (JsonScalarExpr *) node;
+
+ if (walker(jse->expr, context))
+ return true;
+ if (walker(jse->output, context))
+ return true;
+ }
+ break;
case T_JsonSerializeExpr:
{
JsonSerializeExpr *jse = (JsonSerializeExpr *) node;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f610e17aba..796f9c5b4c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -15410,21 +15410,24 @@ json_func_expr:
;
json_parse_expr:
- JSON '(' json_value_expr json_key_uniqueness_constraint_opt ')'
+ JSON '(' json_value_expr json_key_uniqueness_constraint_opt
+ json_returning_clause_opt ')'
{
JsonParseExpr *n = makeNode(JsonParseExpr);
n->expr = (JsonValueExpr *) $3;
n->unique_keys = $4;
+ n->output = (JsonOutput *) $5;
n->location = @1;
$$ = (Node *) n;
}
;
json_scalar_expr:
- JSON_SCALAR '(' a_expr ')'
+ JSON_SCALAR '(' a_expr json_returning_clause_opt ')'
{
JsonScalarExpr *n = makeNode(JsonScalarExpr);
n->expr = (Expr *) $3;
+ n->output = (JsonOutput *) $4;
n->location = @1;
$$ = (Node *) n;
}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index bbd80e5c6e..18622fd013 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4450,19 +4450,48 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
return (Node *) jsexpr;
}
+static JsonReturning *
+transformJsonConstructorRet(ParseState *pstate, JsonOutput *output, const char *fname)
+{
+ JsonReturning *returning;
+
+ if (output)
+ {
+ returning = transformJsonOutput(pstate, output, false);
+
+ Assert(OidIsValid(returning->typid));
+
+ if (returning->typid != JSONOID && returning->typid != JSONBOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot use RETURNING type %s in %s",
+ format_type_be(returning->typid), fname),
+ parser_errposition(pstate, output->typeName->location)));
+ }
+ else
+ {
+ Oid targettype = JSONOID;
+ JsonFormatType format = JS_FORMAT_JSON;
+
+ returning = makeNode(JsonReturning);
+ returning->format = makeJsonFormat(format, JS_ENC_DEFAULT, -1);
+ returning->typid = targettype;
+ returning->typmod = -1;
+ }
+
+ return returning;
+}
+
/*
* Transform a JSON() expression.
*/
static Node *
transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr)
{
- JsonReturning *returning = makeNode(JsonReturning);
+ JsonReturning *returning = transformJsonConstructorRet(pstate, jsexpr->output,
+ "JSON()");
Node *arg;
- returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1);
- returning->typid = JSONOID;
- returning->typmod = -1;
-
if (jsexpr->unique_keys)
{
/*
@@ -4502,12 +4531,9 @@ transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr)
static Node *
transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *jsexpr)
{
- JsonReturning *returning = makeNode(JsonReturning);
Node *arg = transformExprRecurse(pstate, (Node *) jsexpr->expr);
-
- returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1);
- returning->typid = JSONOID;
- returning->typmod = -1;
+ JsonReturning *returning = transformJsonConstructorRet(pstate, jsexpr->output,
+ "JSON_SCALAR()");
if (exprType(arg) == UNKNOWNOID)
arg = coerce_to_specific_type(pstate, arg, TEXTOID, "JSON_SCALAR");
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2f3f59231a..e9b7970acc 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10056,8 +10056,9 @@ get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf)
if (ctor->unique)
appendStringInfoString(buf, " WITH UNIQUE KEYS");
- if (ctor->type != JSCTOR_JSON_PARSE &&
- ctor->type != JSCTOR_JSON_SCALAR)
+ if (!((ctor->type == JSCTOR_JSON_PARSE ||
+ ctor->type == JSCTOR_JSON_SCALAR) &&
+ ctor->returning->typid == JSONOID))
get_json_returning(ctor->returning, buf, true);
}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fb8e5637f7..aecfb6456e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1645,6 +1645,7 @@ typedef struct JsonParseExpr
{
NodeTag type;
JsonValueExpr *expr; /* string expression */
+ JsonOutput *output; /* RETURNING clause, if specified */
bool unique_keys; /* WITH UNIQUE KEYS? */
int location; /* token location, or -1 if unknown */
} JsonParseExpr;
@@ -1657,6 +1658,7 @@ typedef struct JsonScalarExpr
{
NodeTag type;
Expr *expr; /* scalar expression */
+ JsonOutput *output; /* RETURNING clause, if specified */
int location; /* token location, or -1 if unknown */
} JsonScalarExpr;
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 11f5eb2d2c..6cadd87868 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -113,6 +113,49 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITHOUT UNIQUE KEYS);
Output: JSON('123'::json)
(2 rows)
+SELECT JSON('123' RETURNING text);
+ERROR: cannot use RETURNING type text in JSON()
+LINE 1: SELECT JSON('123' RETURNING text);
+ ^
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123');
+ QUERY PLAN
+-----------------------------
+ Result
+ Output: JSON('123'::json)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json);
+ QUERY PLAN
+-----------------------------
+ Result
+ Output: JSON('123'::json)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb);
+ QUERY PLAN
+----------------------------------------------
+ Result
+ Output: JSON('123'::jsonb RETURNING jsonb)
+(2 rows)
+
+SELECT pg_typeof(JSON('123'));
+ pg_typeof
+-----------
+ json
+(1 row)
+
+SELECT pg_typeof(JSON('123' RETURNING json));
+ pg_typeof
+-----------
+ json
+(1 row)
+
+SELECT pg_typeof(JSON('123' RETURNING jsonb));
+ pg_typeof
+-----------
+ jsonb
+(1 row)
+
-- JSON_SCALAR()
SELECT JSON_SCALAR();
ERROR: syntax error at or near ")"
@@ -204,6 +247,20 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123');
Output: JSON_SCALAR('123'::text)
(2 rows)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json);
+ QUERY PLAN
+----------------------------
+ Result
+ Output: JSON_SCALAR(123)
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb);
+ QUERY PLAN
+--------------------------------------------
+ Result
+ Output: JSON_SCALAR(123 RETURNING jsonb)
+(2 rows)
+
-- JSON_SERIALIZE()
SELECT JSON_SERIALIZE();
ERROR: syntax error at or near ")"
diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
index 98bd93c110..51fc659b58 100644
--- a/src/test/regress/sql/sqljson.sql
+++ b/src/test/regress/sql/sqljson.sql
@@ -23,6 +23,14 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON ENCODING UTF8)
EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITH UNIQUE KEYS);
EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITHOUT UNIQUE KEYS);
+SELECT JSON('123' RETURNING text);
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123');
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb);
+SELECT pg_typeof(JSON('123'));
+SELECT pg_typeof(JSON('123' RETURNING json));
+SELECT pg_typeof(JSON('123' RETURNING jsonb));
-- JSON_SCALAR()
SELECT JSON_SCALAR();
@@ -41,6 +49,8 @@ SELECT JSON_SCALAR('{}'::jsonb);
EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123);
EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123');
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb);
-- JSON_SERIALIZE()
SELECT JSON_SERIALIZE();
--
2.25.1
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-25 00:09 Andres Freund <[email protected]>
parent: Andrew Dunstan <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Andres Freund @ 2022-03-25 00:09 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
Hi,
On 2022-03-24 18:51:30 -0400, Andrew Dunstan wrote:
> I wonder if we should add these compile flags to the cfbot's setup?
Yes, I think we should. There's a bit of discussion of that in and below
https://postgr.es/m/20220213051937.GO31460%40telsasoft.com - that veered a bit
of course, so I haven't done anything about it yet. Perhaps one build
COPY_PARSE_PLAN_TREES and RAW_EXPRESSION_COVERAGE_TEST another
WRITE_READ_PARSE_PLAN_TREES? We should add the slower to the macos build,
that's plenty fast and I'm intending to slow the linux test by using ubsan,
which works better on linux.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-27 23:14 Tom Lane <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 2 replies; 95+ messages in thread
From: Tom Lane @ 2022-03-27 23:14 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: [email protected]; Andres Freund <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
I wrote:
>> Andres Freund <[email protected]> writes:
>>> There's also
>>> https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=jabiru&dt=2022-03-22%2022%3A25%3A26
>>> that started failing with
>>> ../../preproc/ecpg --regression -I./../../include -I. -o test1.c test1.pgc
>>> test1.pgc:12: ERROR: syntax error at or near "int"
>>> with this commit.
>> Yeah, I was just scratching my head about that.
This problem came back as soon as we de-reverted that patch :-(.
So much for my guess about unused rules.
What's worse, I'm unable to replicate the failure on an OpenBSD 7.0
system here. So there's something odd about jabiru's build
environment; but what?
regards, tom lane
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 00:21 Andrew Dunstan <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 1 reply; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-28 00:21 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: [email protected]; Andres Freund <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
> On Mar 27, 2022, at 7:14 PM, Tom Lane <[email protected]> wrote:
>
> I wrote:
>>> Andres Freund <[email protected]> writes:
>>>> There's also
>>>> https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=jabiru&dt=2022-03-22%2022%3A25%3A26
>>>> that started failing with
>>>> ../../preproc/ecpg --regression -I./../../include -I. -o test1.c test1.pgc
>>>> test1.pgc:12: ERROR: syntax error at or near "int"
>>>> with this commit.
>
>>> Yeah, I was just scratching my head about that.
>
> This problem came back as soon as we de-reverted that patch :-(.
> So much for my guess about unused rules.
>
> What's worse, I'm unable to replicate the failure on an OpenBSD 7.0
> system here. So there's something odd about jabiru's build
> environment; but what?
>
Very odd. How did it pick up just this commit and not the following one which was pushed at the same time?
Cheers
Andrew
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 00:50 Andres Freund <[email protected]>
parent: Andrew Dunstan <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Andres Freund @ 2022-03-28 00:50 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Tom Lane <[email protected]>; [email protected]; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
Hi,
On 2022-03-27 20:21:05 -0400, Andrew Dunstan wrote:
> Very odd. How did it pick up just this commit and not the following one which was pushed at the same time?
The first recent failing run was with
f4fb45d15c Sun Mar 27 21:03:34 2022 UTC SQL/JSON constructors
f79b803dcc Sun Mar 27 21:03:33 2022 UTC Common SQL/JSON clauses
b64c3bd62e Sun Mar 27 20:26:40 2022 UTC Remove more unused module imports from TAP tests
And then a second failure with:
cc7401d5ca Sun Mar 27 22:32:40 2022 UTC Fix up compiler warnings/errors from f4fb45d15.
Which is what I'd expect?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 01:32 Andrew Dunstan <[email protected]>
parent: Andres Freund <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-28 01:32 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; [email protected]; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 3/27/22 20:50, Andres Freund wrote:
> Hi,
>
> On 2022-03-27 20:21:05 -0400, Andrew Dunstan wrote:
>> Very odd. How did it pick up just this commit and not the following one which was pushed at the same time?
> The first recent failing run was with
> f4fb45d15c Sun Mar 27 21:03:34 2022 UTC SQL/JSON constructors
> f79b803dcc Sun Mar 27 21:03:33 2022 UTC Common SQL/JSON clauses
> b64c3bd62e Sun Mar 27 20:26:40 2022 UTC Remove more unused module imports from TAP tests
>
> And then a second failure with:
> cc7401d5ca Sun Mar 27 22:32:40 2022 UTC Fix up compiler warnings/errors from f4fb45d15.
>
> Which is what I'd expect?
>
Yes, sorry, The url was for the previous commit, not today's. My mistake.
I'll look into it tomorrow, too tired tonight to be very productive.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 11:04 Andrew Dunstan <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 1 reply; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-28 11:04 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: [email protected]; Andres Freund <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 3/27/22 19:14, Tom Lane wrote:
> I wrote:
>>> Andres Freund <[email protected]> writes:
>>>> There's also
>>>> https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=jabiru&dt=2022-03-22%2022%3A25%3A26
>>>> that started failing with
>>>> ../../preproc/ecpg --regression -I./../../include -I. -o test1.c test1.pgc
>>>> test1.pgc:12: ERROR: syntax error at or near "int"
>>>> with this commit.
>>> Yeah, I was just scratching my head about that.
> This problem came back as soon as we de-reverted that patch :-(.
> So much for my guess about unused rules.
>
> What's worse, I'm unable to replicate the failure on an OpenBSD 7.0
> system here. So there's something odd about jabiru's build
> environment; but what?
>
>
It's hard to see how this could be caused by the OS environment. Maybe a
flaky bison/flex? I'm going to be pretty reluctant to revert this based
on this error.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 13:35 Tom Lane <[email protected]>
parent: Andrew Dunstan <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Tom Lane @ 2022-03-28 13:35 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: [email protected]; Andres Freund <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
Andrew Dunstan <[email protected]> writes:
> On 3/27/22 19:14, Tom Lane wrote:
>> What's worse, I'm unable to replicate the failure on an OpenBSD 7.0
>> system here. So there's something odd about jabiru's build
>> environment; but what?
> It's hard to see how this could be caused by the OS environment. Maybe a
> flaky bison/flex? I'm going to be pretty reluctant to revert this based
> on this error.
No, I wouldn't recommend reverting. Perhaps if we dig down and find
something reproducible here, we could fix it --- but right now,
given my failure to reproduce, I think there's just something broken
on jabiru.
regards, tom lane
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 14:30 Andrew Dunstan <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-28 14:30 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: [email protected]; Andres Freund <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 3/28/22 09:35, Tom Lane wrote:
> Andrew Dunstan <[email protected]> writes:
>> On 3/27/22 19:14, Tom Lane wrote:
>>> What's worse, I'm unable to replicate the failure on an OpenBSD 7.0
>>> system here. So there's something odd about jabiru's build
>>> environment; but what?
>> It's hard to see how this could be caused by the OS environment. Maybe a
>> flaky bison/flex? I'm going to be pretty reluctant to revert this based
>> on this error.
> No, I wouldn't recommend reverting. Perhaps if we dig down and find
> something reproducible here, we could fix it --- but right now,
> given my failure to reproduce, I think there's just something broken
> on jabiru.
>
>
Yeah. I have just duplicated your non-replication on a fresh instance.
Nikola Ivanov, can you give us any assistance or give us access to the
machine?
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 15:05 Nikola Ivanov <[email protected]>
parent: Andrew Dunstan <[email protected]>
0 siblings, 2 replies; 95+ messages in thread
From: Nikola Ivanov @ 2022-03-28 15:05 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Tom Lane <[email protected]>; Nikola Ivanov <[email protected]>; Andres Freund <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
Hi Andrew,
Let me know check what can I do with the access. I will get back to you in
an hour.
Regards
On Mon, Mar 28, 2022, 17:30 Andrew Dunstan <[email protected]> wrote:
>
> On 3/28/22 09:35, Tom Lane wrote:
> > Andrew Dunstan <[email protected]> writes:
> >> On 3/27/22 19:14, Tom Lane wrote:
> >>> What's worse, I'm unable to replicate the failure on an OpenBSD 7.0
> >>> system here. So there's something odd about jabiru's build
> >>> environment; but what?
> >> It's hard to see how this could be caused by the OS environment. Maybe a
> >> flaky bison/flex? I'm going to be pretty reluctant to revert this based
> >> on this error.
> > No, I wouldn't recommend reverting. Perhaps if we dig down and find
> > something reproducible here, we could fix it --- but right now,
> > given my failure to reproduce, I think there's just something broken
> > on jabiru.
> >
> >
>
>
>
> Yeah. I have just duplicated your non-replication on a fresh instance.
>
>
> Nikola Ivanov, can you give us any assistance or give us access to the
> machine?
>
>
> cheers
>
>
> andrew
>
>
> --
> Andrew Dunstan
> EDB: https://www.enterprisedb.com
>
>
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 15:25 Andrew Dunstan <[email protected]>
parent: Nikola Ivanov <[email protected]>
1 sibling, 0 replies; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-28 15:25 UTC (permalink / raw)
To: Nikola Ivanov <[email protected]>; +Cc: Tom Lane <[email protected]>; Nikola Ivanov <[email protected]>; Andres Freund <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 3/28/22 11:05, Nikola Ivanov wrote:
> Hi Andrew,
>
> Let me know check what can I do with the access. I will get back to
> you in an hour.
Thanks for you help and prompt response.
In the first instance we'd like to know what might be different about
jabiru from the openbsd7/clang11 instances Tom and I have just
successfully tested on. In the last resort we might need to run ecpg
under a debugger on jabiru to see why it's failing there and not
elsewhere. To set up for that run the buildfarm script with --keepall.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 15:39 Andres Freund <[email protected]>
parent: Nikola Ivanov <[email protected]>
1 sibling, 1 reply; 95+ messages in thread
From: Andres Freund @ 2022-03-28 15:39 UTC (permalink / raw)
To: Nikola Ivanov <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Tom Lane <[email protected]>; Nikola Ivanov <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
Hi,
On 2022-03-28 18:05:19 +0300, Nikola Ivanov wrote:
> Let me know check what can I do with the access. I will get back to you in
> an hour.
Perhaps you can temporarily enable keep_error_builds, and send in
src/interfaces/ecpg/preproc/c_kwlist_d.h
src/interfaces/ecpg/preproc/pgc.c
src/interfaces/ecpg/preproc/preproc.h
src/interfaces/ecpg/preproc/ecpg_kwlist_d.h
src/interfaces/ecpg/preproc/preproc.c
from the failed build directory? It seems something there have to differ.
Regards,
Andres
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 15:50 Nikola Ivanov <[email protected]>
parent: Andres Freund <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Nikola Ivanov @ 2022-03-28 15:50 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Tom Lane <[email protected]>; Nikola Ivanov <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
ok, I have enabled it. Will send it after the next build.
Regards
On Mon, 28 Mar 2022 at 18:39, Andres Freund <[email protected]> wrote:
> Hi,
>
> On 2022-03-28 18:05:19 +0300, Nikola Ivanov wrote:
> > Let me know check what can I do with the access. I will get back to you
> in
> > an hour.
>
> Perhaps you can temporarily enable keep_error_builds, and send in
> src/interfaces/ecpg/preproc/c_kwlist_d.h
> src/interfaces/ecpg/preproc/pgc.c
> src/interfaces/ecpg/preproc/preproc.h
> src/interfaces/ecpg/preproc/ecpg_kwlist_d.h
> src/interfaces/ecpg/preproc/preproc.c
> from the failed build directory? It seems something there have to differ.
>
> Regards,
>
> Andres
>
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 18:31 Nikola Ivanov <[email protected]>
parent: Nikola Ivanov <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Nikola Ivanov @ 2022-03-28 18:31 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Tom Lane <[email protected]>; Nikola Ivanov <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
Hi Andreas,
Archive with the files is attached.
On Mon, 28 Mar 2022 at 18:50, Nikola Ivanov <[email protected]> wrote:
> ok, I have enabled it. Will send it after the next build.
>
>
> Regards
>
> On Mon, 28 Mar 2022 at 18:39, Andres Freund <[email protected]> wrote:
>
>> Hi,
>>
>> On 2022-03-28 18:05:19 +0300, Nikola Ivanov wrote:
>> > Let me know check what can I do with the access. I will get back to you
>> in
>> > an hour.
>>
>> Perhaps you can temporarily enable keep_error_builds, and send in
>> src/interfaces/ecpg/preproc/c_kwlist_d.h
>> src/interfaces/ecpg/preproc/pgc.c
>> src/interfaces/ecpg/preproc/preproc.h
>> src/interfaces/ecpg/preproc/ecpg_kwlist_d.h
>> src/interfaces/ecpg/preproc/preproc.c
>> from the failed build directory? It seems something there have to differ.
>>
>> Regards,
>>
>> Andres
>>
>
Attachments:
[application/gzip] jabiru_2022-03-28_20-25-16.tar.gz (435.5K, ../../CABB9jwOumbSeqPFWae2_xSjE89CY_Rv4tZXX2YieSZZ++mHNDw@mail.gmail.com/3-jabiru_2022-03-28_20-25-16.tar.gz)
download
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 18:57 Andrew Dunstan <[email protected]>
parent: Nikola Ivanov <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-28 18:57 UTC (permalink / raw)
To: Nikola Ivanov <[email protected]>; Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; Nikola Ivanov <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 3/28/22 14:31, Nikola Ivanov wrote:
> Hi Andreas,
>
> Archive with the files is attached.
That didn't help, there are no differences that matter (just #line
directives as I did a vpath build). :-(
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 19:27 Andres Freund <[email protected]>
parent: Andrew Dunstan <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Andres Freund @ 2022-03-28 19:27 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Nikola Ivanov <[email protected]>; Tom Lane <[email protected]>; Nikola Ivanov <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
Hi,
On 2022-03-28 14:57:20 -0400, Andrew Dunstan wrote:
> That didn't help, there are no differences that matter (just #line
> directives as I did a vpath build). :-(
Yea. I didn't see any differences when comparing to a non-vpath build that
runs tests successfully. Pretty weird.
Nikola, unless remote access turns out to be possible for one of us, could you
perhaps try to build interactively and see whether it reproduces there?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 19:36 Tom Lane <[email protected]>
parent: Andres Freund <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Tom Lane @ 2022-03-28 19:36 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Nikola Ivanov <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
Andres Freund <[email protected]> writes:
> On 2022-03-28 14:57:20 -0400, Andrew Dunstan wrote:
>> That didn't help, there are no differences that matter (just #line
>> directives as I did a vpath build). :-(
> Yea. I didn't see any differences when comparing to a non-vpath build that
> runs tests successfully. Pretty weird.
Unsurprisingly, these files match what I built, too. So the problem
is downstream of the flex/bison runs. Baffling :-(
regards, tom lane
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-28 23:25 Tom Lane <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 2 replies; 95+ messages in thread
From: Tom Lane @ 2022-03-28 23:25 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Andres Freund <[email protected]>; Nikola Ivanov <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
... even more baffling: jabiru went green after the IS JSON patch.
regards, tom lane
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-29 01:03 Andrew Dunstan <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 1 reply; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-29 01:03 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Andres Freund <[email protected]>; Nikola Ivanov <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
> On Mar 28, 2022, at 7:25 PM, Tom Lane <[email protected]> wrote:
>
> ... even more baffling: jabiru went green after the IS JSON patch.
>
>
Yeah, bizarre. Let’s see if I can upset that tomorrow with the next patch :-)
cheers
andrew
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-29 03:41 Andres Freund <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 1 reply; 95+ messages in thread
From: Andres Freund @ 2022-03-29 03:41 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Nikola Ivanov <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 2022-03-28 19:25:51 -0400, Tom Lane wrote:
> ... even more baffling: jabiru went green after the IS JSON patch.
Broken ccache contents somehow?
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-29 04:09 Nikola Ivanov <[email protected]>
parent: Andres Freund <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Nikola Ivanov @ 2022-03-29 04:09 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; Andrew Dunstan <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
Yes, it seems it was the ccache - I had enabled the 'ccache_failure_remove'
flag last night and run a build, which failed, but the builds after that
went green.
Regards
On Tue, 29 Mar 2022 at 06:41, Andres Freund <[email protected]> wrote:
> On 2022-03-28 19:25:51 -0400, Tom Lane wrote:
> > ... even more baffling: jabiru went green after the IS JSON patch.
>
> Broken ccache contents somehow?
>
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-31 19:54 Greg Stark <[email protected]>
parent: Andrew Dunstan <[email protected]>
0 siblings, 1 reply; 95+ messages in thread
From: Greg Stark @ 2022-03-31 19:54 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Tom Lane <[email protected]>; Andres Freund <[email protected]>; Nikola Ivanov <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
I see several commits referencing this entry. Can we mark it committed
or are there still chunks of commits to go?
^ permalink raw reply [nested|flat] 95+ messages in thread
* Re: SQL/JSON: functions
@ 2022-03-31 20:25 Andrew Dunstan <[email protected]>
parent: Greg Stark <[email protected]>
0 siblings, 0 replies; 95+ messages in thread
From: Andrew Dunstan @ 2022-03-31 20:25 UTC (permalink / raw)
To: Greg Stark <[email protected]>; +Cc: Tom Lane <[email protected]>; Andres Freund <[email protected]>; Nikola Ivanov <[email protected]>; Himanshu Upadhyaya <[email protected]>; Nikita Glukhov <[email protected]>; PostgreSQL-development <[email protected]>; Dmitry Dolgov <[email protected]>; Oleg Bartunov <[email protected]>; Erik Rijkers <[email protected]>
On 3/31/22 15:54, Greg Stark wrote:
> I see several commits referencing this entry. Can we mark it committed
> or are there still chunks of commits to go?
No code chunks left, only a documentation patch which should land
tomorrow or Saturday.
I am also planning on committing the JSON_TABLE patches before feature
freeze (April 7). They depend on this set.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 95+ messages in thread
end of thread, other threads:[~2022-03-31 20:25 UTC | newest]
Thread overview: 95+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v27 04/11] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v30 04/11] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v20 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v26 05/11] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v35 4/7] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v37 04/11] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v32 04/11] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v31 04/11] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v18 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v28 04/11] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v19 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v25 05/11] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v23 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v24 05/11] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v21 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v22 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v33 04/11] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v36 4/7] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2020-03-09 03:57 [PATCH v34 04/15] pg_ls_tmpdir to show directories and "isdir" argument.. Justin Pryzby <[email protected]>
2022-03-04 16:28 Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-05 14:39 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-22 22:16 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-22 22:20 ` Re: SQL/JSON: functions Tom Lane <[email protected]>
2022-03-22 22:31 ` Re: SQL/JSON: functions Tom Lane <[email protected]>
2022-03-22 23:33 ` Re: SQL/JSON: functions Andres Freund <[email protected]>
2022-03-22 23:48 ` Re: SQL/JSON: functions Tom Lane <[email protected]>
2022-03-23 01:07 ` Re: SQL/JSON: functions Tom Lane <[email protected]>
2022-03-27 23:14 ` Re: SQL/JSON: functions Tom Lane <[email protected]>
2022-03-28 00:21 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-28 00:50 ` Re: SQL/JSON: functions Andres Freund <[email protected]>
2022-03-28 01:32 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-28 11:04 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-28 13:35 ` Re: SQL/JSON: functions Tom Lane <[email protected]>
2022-03-28 14:30 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-28 15:05 ` Re: SQL/JSON: functions Nikola Ivanov <[email protected]>
2022-03-28 15:25 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-28 15:39 ` Re: SQL/JSON: functions Andres Freund <[email protected]>
2022-03-28 15:50 ` Re: SQL/JSON: functions Nikola Ivanov <[email protected]>
2022-03-28 18:31 ` Re: SQL/JSON: functions Nikola Ivanov <[email protected]>
2022-03-28 18:57 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-28 19:27 ` Re: SQL/JSON: functions Andres Freund <[email protected]>
2022-03-28 19:36 ` Re: SQL/JSON: functions Tom Lane <[email protected]>
2022-03-28 23:25 ` Re: SQL/JSON: functions Tom Lane <[email protected]>
2022-03-29 01:03 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-31 19:54 ` Re: SQL/JSON: functions Greg Stark <[email protected]>
2022-03-31 20:25 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-29 03:41 ` Re: SQL/JSON: functions Andres Freund <[email protected]>
2022-03-29 04:09 ` Re: SQL/JSON: functions Nikola Ivanov <[email protected]>
2022-03-22 23:56 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-23 12:24 ` Re: SQL/JSON: functions Justin Pryzby <[email protected]>
2022-03-23 19:49 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-23 20:23 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-23 20:56 ` Re: SQL/JSON: functions Justin Pryzby <[email protected]>
2022-03-24 22:51 ` Re: SQL/JSON: functions Andrew Dunstan <[email protected]>
2022-03-25 00:09 ` Re: SQL/JSON: functions Andres Freund <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox