diff --git a/contrib/jsonpathx/Makefile b/contrib/jsonpathx/Makefile
new file mode 100644
index 0000000000..5ba50b8b25
--- /dev/null
+++ b/contrib/jsonpathx/Makefile
@@ -0,0 +1,22 @@
+# contrib/jsonpathx/Makefile
+
+MODULE_big = jsonpathx
+OBJS = jsonpathx.o
+
+EXTENSION = jsonpathx
+DATA = jsonpathx--1.0.sql
+PGFILEDESC = "jsonpathx - extended JSON path item methods"
+
+REGRESS = jsonpathx_jsonb jsonpathx_json
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/jsonpathx
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
diff --git a/contrib/jsonpathx/expected/jsonpathx_json.out b/contrib/jsonpathx/expected/jsonpathx_json.out
new file mode 100644
index 0000000000..c7e9d4ea61
--- /dev/null
+++ b/contrib/jsonpathx/expected/jsonpathx_json.out
@@ -0,0 +1,439 @@
+CREATE EXTENSION jsonpathx;
+-- map item method
+select json_path_query('1', 'strict $.map(x => x + 10)');
+ERROR: SQL/JSON array not found
+DETAIL: jsonpath .map() is applied to not an array
+select json_path_query('1', 'lax $.map(x => x + 10)');
+ json_path_query
+-----------------
+ 11
+(1 row)
+
+select json_path_query('[1, 2, 3]', '$.map(x => x + 10)');
+ json_path_query
+-----------------
+ [11, 12, 13]
+(1 row)
+
+select json_path_query('[1, 2, 3]', '$.map(x => x + 10)[*]');
+ json_path_query
+-----------------
+ 11
+ 12
+ 13
+(3 rows)
+
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.map(a => a.map(x => x + 10))');
+ json_path_query
+----------------------------------------
+ [[11, 12], [13, 14, 15], [], [16, 17]]
+(1 row)
+
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.flatmap(a => a.map(a => a + 10))');
+ json_path_query
+------------------------------
+ [11, 12, 13, 14, 15, 16, 17]
+(1 row)
+
+-- map function
+select json_path_query('1', 'strict map($, x => x + 10)');
+ json_path_query
+-----------------
+ 11
+(1 row)
+
+select json_path_query('1', 'lax map($, x => x + 10)');
+ json_path_query
+-----------------
+ 11
+(1 row)
+
+select json_path_query('[1, 2, 3]', 'map($[*], x => x + 10)');
+ json_path_query
+-----------------
+ 11
+ 12
+ 13
+(3 rows)
+
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'map($[*], x => [map(x[*], x => x + 10)])');
+ json_path_query
+-----------------
+ [11, 12]
+ [13, 14, 15]
+ []
+ [16, 17]
+(4 rows)
+
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'flatmap($[*], a => map(a[*], x => x + 10))');
+ json_path_query
+-----------------
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+(7 rows)
+
+-- reduce/fold item methods
+select json_path_query('1', 'strict $.reduce((x, y) => x + y)');
+ERROR: SQL/JSON array not found
+DETAIL: jsonpath .reduce() is applied to not an array
+select json_path_query('1', 'lax $.reduce((x, y) => x + y)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('1', 'strict $.fold((x, y) => x + y, 10)');
+ERROR: SQL/JSON array not found
+DETAIL: jsonpath .fold() is applied to not an array
+select json_path_query('1', 'lax $.fold((x, y) => x + y, 10)');
+ json_path_query
+-----------------
+ 11
+(1 row)
+
+select json_path_query('[1, 2, 3]', '$.reduce((x, y) => x + y)');
+ json_path_query
+-----------------
+ 6
+(1 row)
+
+select json_path_query('[1, 2, 3]', '$.fold((x, y) => x + y, 100)');
+ json_path_query
+-----------------
+ 106
+(1 row)
+
+select json_path_query('[]', '$.reduce((x, y) => x + y)');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('[]', '$.fold((x, y) => x + y, 100)');
+ json_path_query
+-----------------
+ 100
+(1 row)
+
+select json_path_query('[1]', '$.reduce((x, y) => x + y)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('[1, 2, 3]', '$.foldl((x, y) => [x, y], [])');
+ json_path_query
+-------------------
+ [[[[], 1], 2], 3]
+(1 row)
+
+select json_path_query('[1, 2, 3]', '$.foldr((x, y) => [y, x], [])');
+ json_path_query
+-------------------
+ [[[[], 3], 2], 1]
+(1 row)
+
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.fold((x, y) => x + y.fold((a, b) => a + b, 100), 1000)');
+ json_path_query
+-----------------
+ 1428
+(1 row)
+
+-- reduce/fold functions
+select json_path_query('1', 'strict reduce($, (x, y) => x + y)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('1', 'lax reduce($, (x, y) => x + y)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('1', 'strict fold($, (x, y) => x + y, 10)');
+ json_path_query
+-----------------
+ 11
+(1 row)
+
+select json_path_query('1', 'lax fold($, (x, y) => x + y, 10)');
+ json_path_query
+-----------------
+ 11
+(1 row)
+
+select json_path_query('[1, 2, 3]', 'reduce($[*], (x, y) => x + y)');
+ json_path_query
+-----------------
+ 6
+(1 row)
+
+select json_path_query('[1, 2, 3]', 'fold($[*], (x, y) => x + y, 100)');
+ json_path_query
+-----------------
+ 106
+(1 row)
+
+select json_path_query('[]', 'reduce($[*], (x, y) => x + y)');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('[]', 'fold($[*], (x, y) => x + y, 100)');
+ json_path_query
+-----------------
+ 100
+(1 row)
+
+select json_path_query('[1]', 'reduce($[*], (x, y) => x + y)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('[1, 2, 3]', 'foldl($[*], (x, y) => [x, y], [])');
+ json_path_query
+-------------------
+ [[[[], 1], 2], 3]
+(1 row)
+
+select json_path_query('[1, 2, 3]', 'foldr($[*], (x, y) => [y, x], [])');
+ json_path_query
+-------------------
+ [[[[], 3], 2], 1]
+(1 row)
+
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'fold($[*], (x, y) => x + y.fold((a, b) => a + b, 100), 1000)');
+ json_path_query
+-----------------
+ 1428
+(1 row)
+
+-- min/max item methods
+select json_path_query('1', 'strict $.min()');
+ERROR: SQL/JSON array not found
+DETAIL: jsonpath .min() is applied to not an array
+select json_path_query('1', 'lax $.min()');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('[]', '$.min()');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('[]', '$.max()');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('[null]', '$.min()');
+ json_path_query
+-----------------
+ null
+(1 row)
+
+select json_path_query('[null]', '$.max()');
+ json_path_query
+-----------------
+ null
+(1 row)
+
+select json_path_query('[1, 2, 3]', '$.min()');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('[1, 2, 3]', '$.max()');
+ json_path_query
+-----------------
+ 3
+(1 row)
+
+select json_path_query('[2, 3, 5, null, 1, 4, null]', '$.min()');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('[2, 3, 5, null, 1, 4, null]', '$.max()');
+ json_path_query
+-----------------
+ 5
+(1 row)
+
+select json_path_query('["aa", null, "a", "bbb"]', '$.min()');
+ json_path_query
+-----------------
+ "a"
+(1 row)
+
+select json_path_query('["aa", null, "a", "bbb"]', '$.max()');
+ json_path_query
+-----------------
+ "bbb"
+(1 row)
+
+select json_path_query('[1, null, "2"]', '$.max()');
+ERROR: SQL/JSON scalar required
+DETAIL: boolean lambda expression in jsonpath .max() returned Unknown
+-- min/max functions
+select json_path_query('1', 'strict min($)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('1', 'lax min($)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('[]', 'min($[*])');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('[]', 'max($[*])');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('[null]', 'min($[*])');
+ json_path_query
+-----------------
+ null
+(1 row)
+
+select json_path_query('[null]', 'max($[*])');
+ json_path_query
+-----------------
+ null
+(1 row)
+
+select json_path_query('[1, 2, 3]', 'min($[*])');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('[1, 2, 3]', 'max($[*])');
+ json_path_query
+-----------------
+ 3
+(1 row)
+
+select json_path_query('[2, 3, 5, null, 1, 4, null]', 'min($[*])');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('[2, 3, 5, null, 1, 4, null]', 'max($[*])');
+ json_path_query
+-----------------
+ 5
+(1 row)
+
+select json_path_query('["aa", null, "a", "bbb"]', 'min($[*])');
+ json_path_query
+-----------------
+ "a"
+(1 row)
+
+select json_path_query('["aa", null, "a", "bbb"]', 'max($[*])');
+ json_path_query
+-----------------
+ "bbb"
+(1 row)
+
+select json_path_query('[1, null, "2"]', 'max($[*])');
+ERROR: SQL/JSON scalar required
+DETAIL: boolean lambda expression in jsonpath .max() returned Unknown
+-- tests for simplified variable-based lambda syntax
+select json_path_query('[1, 2, 3]', '$.map($1 + 100)');
+ json_path_query
+-----------------
+ [101, 102, 103]
+(1 row)
+
+select json_path_query('[1, 2, 3]', 'map($[*], $1 + 100)');
+ json_path_query
+-----------------
+ 101
+ 102
+ 103
+(3 rows)
+
+select json_path_query('[1, 2, 3]', '$.reduce($1 + $2)');
+ json_path_query
+-----------------
+ 6
+(1 row)
+
+select json_path_query('[1, 2, 3]', 'reduce($[*], $1 + $2)');
+ json_path_query
+-----------------
+ 6
+(1 row)
+
+select json_path_query('[1, 2, 3]', '$.fold($1 + $2, 100)');
+ json_path_query
+-----------------
+ 106
+(1 row)
+
+select json_path_query('[1, 2, 3]', 'fold($[*], $1 + $2, 100)');
+ json_path_query
+-----------------
+ 106
+(1 row)
+
+-- more complex tests
+select json_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.map((x,i,a) => {n: i, sum: reduce(a[0 to i], (x,y) => x + y)})[*]');
+ json_path_query
+---------------------
+ {"n": 0, "sum": 0}
+ {"n": 1, "sum": 1}
+ {"n": 2, "sum": 3}
+ {"n": 3, "sum": 6}
+ {"n": 4, "sum": 10}
+ {"n": 5, "sum": 15}
+ {"n": 6, "sum": 21}
+ {"n": 7, "sum": 28}
+ {"n": 8, "sum": 36}
+ {"n": 9, "sum": 45}
+(10 rows)
+
+select json_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y,i,a) => [x[*], {n:y, s: [a[0 to i]].reduce($1+$2)}], [])[*]');
+ json_path_query
+-------------------
+ {"n": 0, "s": 0}
+ {"n": 1, "s": 1}
+ {"n": 2, "s": 3}
+ {"n": 3, "s": 6}
+ {"n": 4, "s": 10}
+ {"n": 5, "s": 15}
+ {"n": 6, "s": 21}
+ {"n": 7, "s": 28}
+ {"n": 8, "s": 36}
+ {"n": 9, "s": 45}
+(10 rows)
+
+select json_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y) => [y,y,y].map((a) => a + y).reduce((x,y)=>x+y) + x * 100, 0)');
+ json_path_query
+-------------------
+ 61218243036424854
+(1 row)
+
+DROP EXTENSION jsonpathx;
diff --git a/contrib/jsonpathx/expected/jsonpathx_jsonb.out b/contrib/jsonpathx/expected/jsonpathx_jsonb.out
new file mode 100644
index 0000000000..44aa15f3cb
--- /dev/null
+++ b/contrib/jsonpathx/expected/jsonpathx_jsonb.out
@@ -0,0 +1,439 @@
+CREATE EXTENSION jsonpathx;
+-- map item method
+select jsonb_path_query('1', 'strict $.map(x => x + 10)');
+ERROR: SQL/JSON array not found
+DETAIL: jsonpath .map() is applied to not an array
+select jsonb_path_query('1', 'lax $.map(x => x + 10)');
+ jsonb_path_query
+------------------
+ 11
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '$.map(x => x + 10)');
+ jsonb_path_query
+------------------
+ [11, 12, 13]
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '$.map(x => x + 10)[*]');
+ jsonb_path_query
+------------------
+ 11
+ 12
+ 13
+(3 rows)
+
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.map(a => a.map(x => x + 10))');
+ jsonb_path_query
+----------------------------------------
+ [[11, 12], [13, 14, 15], [], [16, 17]]
+(1 row)
+
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.flatmap(a => a.map(a => a + 10))');
+ jsonb_path_query
+------------------------------
+ [11, 12, 13, 14, 15, 16, 17]
+(1 row)
+
+-- map function
+select jsonb_path_query('1', 'strict map($, x => x + 10)');
+ jsonb_path_query
+------------------
+ 11
+(1 row)
+
+select jsonb_path_query('1', 'lax map($, x => x + 10)');
+ jsonb_path_query
+------------------
+ 11
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'map($[*], x => x + 10)');
+ jsonb_path_query
+------------------
+ 11
+ 12
+ 13
+(3 rows)
+
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'map($[*], x => [map(x[*], x => x + 10)])');
+ jsonb_path_query
+------------------
+ [11, 12]
+ [13, 14, 15]
+ []
+ [16, 17]
+(4 rows)
+
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'flatmap($[*], a => map(a[*], x => x + 10))');
+ jsonb_path_query
+------------------
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+(7 rows)
+
+-- reduce/fold item methods
+select jsonb_path_query('1', 'strict $.reduce((x, y) => x + y)');
+ERROR: SQL/JSON array not found
+DETAIL: jsonpath .reduce() is applied to not an array
+select jsonb_path_query('1', 'lax $.reduce((x, y) => x + y)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('1', 'strict $.fold((x, y) => x + y, 10)');
+ERROR: SQL/JSON array not found
+DETAIL: jsonpath .fold() is applied to not an array
+select jsonb_path_query('1', 'lax $.fold((x, y) => x + y, 10)');
+ jsonb_path_query
+------------------
+ 11
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '$.reduce((x, y) => x + y)');
+ jsonb_path_query
+------------------
+ 6
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '$.fold((x, y) => x + y, 100)');
+ jsonb_path_query
+------------------
+ 106
+(1 row)
+
+select jsonb_path_query('[]', '$.reduce((x, y) => x + y)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', '$.fold((x, y) => x + y, 100)');
+ jsonb_path_query
+------------------
+ 100
+(1 row)
+
+select jsonb_path_query('[1]', '$.reduce((x, y) => x + y)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '$.foldl((x, y) => [x, y], [])');
+ jsonb_path_query
+-------------------
+ [[[[], 1], 2], 3]
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '$.foldr((x, y) => [y, x], [])');
+ jsonb_path_query
+-------------------
+ [[[[], 3], 2], 1]
+(1 row)
+
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.fold((x, y) => x + y.fold((a, b) => a + b, 100), 1000)');
+ jsonb_path_query
+------------------
+ 1428
+(1 row)
+
+-- reduce/fold functions
+select jsonb_path_query('1', 'strict reduce($, (x, y) => x + y)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('1', 'lax reduce($, (x, y) => x + y)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('1', 'strict fold($, (x, y) => x + y, 10)');
+ jsonb_path_query
+------------------
+ 11
+(1 row)
+
+select jsonb_path_query('1', 'lax fold($, (x, y) => x + y, 10)');
+ jsonb_path_query
+------------------
+ 11
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'reduce($[*], (x, y) => x + y)');
+ jsonb_path_query
+------------------
+ 6
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'fold($[*], (x, y) => x + y, 100)');
+ jsonb_path_query
+------------------
+ 106
+(1 row)
+
+select jsonb_path_query('[]', 'reduce($[*], (x, y) => x + y)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'fold($[*], (x, y) => x + y, 100)');
+ jsonb_path_query
+------------------
+ 100
+(1 row)
+
+select jsonb_path_query('[1]', 'reduce($[*], (x, y) => x + y)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'foldl($[*], (x, y) => [x, y], [])');
+ jsonb_path_query
+-------------------
+ [[[[], 1], 2], 3]
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'foldr($[*], (x, y) => [y, x], [])');
+ jsonb_path_query
+-------------------
+ [[[[], 3], 2], 1]
+(1 row)
+
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'fold($[*], (x, y) => x + y.fold((a, b) => a + b, 100), 1000)');
+ jsonb_path_query
+------------------
+ 1428
+(1 row)
+
+-- min/max item methods
+select jsonb_path_query('1', 'strict $.min()');
+ERROR: SQL/JSON array not found
+DETAIL: jsonpath .min() is applied to not an array
+select jsonb_path_query('1', 'lax $.min()');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[]', '$.min()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', '$.max()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[null]', '$.min()');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select jsonb_path_query('[null]', '$.max()');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '$.min()');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '$.max()');
+ jsonb_path_query
+------------------
+ 3
+(1 row)
+
+select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', '$.min()');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', '$.max()');
+ jsonb_path_query
+------------------
+ 5
+(1 row)
+
+select jsonb_path_query('["aa", null, "a", "bbb"]', '$.min()');
+ jsonb_path_query
+------------------
+ "a"
+(1 row)
+
+select jsonb_path_query('["aa", null, "a", "bbb"]', '$.max()');
+ jsonb_path_query
+------------------
+ "bbb"
+(1 row)
+
+select jsonb_path_query('[1, null, "2"]', '$.max()');
+ERROR: SQL/JSON scalar required
+DETAIL: boolean lambda expression in jsonpath .max() returned Unknown
+-- min/max functions
+select jsonb_path_query('1', 'strict min($)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('1', 'lax min($)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[]', 'min($[*])');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'max($[*])');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[null]', 'min($[*])');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select jsonb_path_query('[null]', 'max($[*])');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'min($[*])');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'max($[*])');
+ jsonb_path_query
+------------------
+ 3
+(1 row)
+
+select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', 'min($[*])');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', 'max($[*])');
+ jsonb_path_query
+------------------
+ 5
+(1 row)
+
+select jsonb_path_query('["aa", null, "a", "bbb"]', 'min($[*])');
+ jsonb_path_query
+------------------
+ "a"
+(1 row)
+
+select jsonb_path_query('["aa", null, "a", "bbb"]', 'max($[*])');
+ jsonb_path_query
+------------------
+ "bbb"
+(1 row)
+
+select jsonb_path_query('[1, null, "2"]', 'max($[*])');
+ERROR: SQL/JSON scalar required
+DETAIL: boolean lambda expression in jsonpath .max() returned Unknown
+-- tests for simplified variable-based lambda syntax
+select jsonb_path_query('[1, 2, 3]', '$.map($1 + 100)');
+ jsonb_path_query
+------------------
+ [101, 102, 103]
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'map($[*], $1 + 100)');
+ jsonb_path_query
+------------------
+ 101
+ 102
+ 103
+(3 rows)
+
+select jsonb_path_query('[1, 2, 3]', '$.reduce($1 + $2)');
+ jsonb_path_query
+------------------
+ 6
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'reduce($[*], $1 + $2)');
+ jsonb_path_query
+------------------
+ 6
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '$.fold($1 + $2, 100)');
+ jsonb_path_query
+------------------
+ 106
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'fold($[*], $1 + $2, 100)');
+ jsonb_path_query
+------------------
+ 106
+(1 row)
+
+-- more complex tests
+select jsonb_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.map((x,i,a) => {n: i, sum: reduce(a[0 to i], (x,y) => x + y)})[*]');
+ jsonb_path_query
+---------------------
+ {"n": 0, "sum": 0}
+ {"n": 1, "sum": 1}
+ {"n": 2, "sum": 3}
+ {"n": 3, "sum": 6}
+ {"n": 4, "sum": 10}
+ {"n": 5, "sum": 15}
+ {"n": 6, "sum": 21}
+ {"n": 7, "sum": 28}
+ {"n": 8, "sum": 36}
+ {"n": 9, "sum": 45}
+(10 rows)
+
+select jsonb_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y,i,a) => [x[*], {n:y, s: [a[0 to i]].reduce($1+$2)}], [])[*]');
+ jsonb_path_query
+-------------------
+ {"n": 0, "s": 0}
+ {"n": 1, "s": 1}
+ {"n": 2, "s": 3}
+ {"n": 3, "s": 6}
+ {"n": 4, "s": 10}
+ {"n": 5, "s": 15}
+ {"n": 6, "s": 21}
+ {"n": 7, "s": 28}
+ {"n": 8, "s": 36}
+ {"n": 9, "s": 45}
+(10 rows)
+
+select jsonb_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y) => [y,y,y].map((a) => a + y).reduce((x,y)=>x+y) + x * 100, 0)');
+ jsonb_path_query
+-------------------
+ 61218243036424854
+(1 row)
+
+DROP EXTENSION jsonpathx;
diff --git a/contrib/jsonpathx/jsonpathx--1.0.sql b/contrib/jsonpathx/jsonpathx--1.0.sql
new file mode 100644
index 0000000000..0fb200fd9b
--- /dev/null
+++ b/contrib/jsonpathx/jsonpathx--1.0.sql
@@ -0,0 +1,44 @@
+/* contrib/jsonpathx/jsonpathx--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION jsonpathx" to load this file. \quit
+
+CREATE FUNCTION map(jsonpath_fcxt)
+RETURNS int8
+AS 'MODULE_PATHNAME', 'jsonpath_map'
+LANGUAGE C STRICT STABLE;
+
+CREATE FUNCTION flatmap(jsonpath_fcxt)
+RETURNS int8
+AS 'MODULE_PATHNAME', 'jsonpath_flatmap'
+LANGUAGE C STRICT STABLE;
+
+CREATE FUNCTION reduce(jsonpath_fcxt)
+RETURNS int8
+AS 'MODULE_PATHNAME', 'jsonpath_reduce'
+LANGUAGE C STRICT STABLE;
+
+CREATE FUNCTION fold(jsonpath_fcxt)
+RETURNS int8
+AS 'MODULE_PATHNAME', 'jsonpath_fold'
+LANGUAGE C STRICT STABLE;
+
+CREATE FUNCTION foldl(jsonpath_fcxt)
+RETURNS int8
+AS 'MODULE_PATHNAME', 'jsonpath_foldl'
+LANGUAGE C STRICT STABLE;
+
+CREATE FUNCTION foldr(jsonpath_fcxt)
+RETURNS int8
+AS 'MODULE_PATHNAME', 'jsonpath_foldr'
+LANGUAGE C STRICT STABLE;
+
+CREATE FUNCTION min(jsonpath_fcxt)
+RETURNS int8
+AS 'MODULE_PATHNAME', 'jsonpath_min'
+LANGUAGE C STRICT STABLE;
+
+CREATE FUNCTION max(jsonpath_fcxt)
+RETURNS int8
+AS 'MODULE_PATHNAME', 'jsonpath_max'
+LANGUAGE C STRICT STABLE;
diff --git a/contrib/jsonpathx/jsonpathx.c b/contrib/jsonpathx/jsonpathx.c
new file mode 100644
index 0000000000..c0b2fae900
--- /dev/null
+++ b/contrib/jsonpathx/jsonpathx.c
@@ -0,0 +1,842 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpathx.c
+ * Extended jsonpath item methods and operators for jsonb type.
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/jsonpathx/jsonpathx.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "fmgr.h"
+#include "nodes/pg_list.h"
+#include "utils/builtins.h"
+#include "utils/jsonpath.h"
+
+PG_MODULE_MAGIC;
+
+static JsonPathExecResult
+jspThrowComparisonError(JsonPathExecContext *cxt, const char *methodName)
+{
+ if (jspThrowErrors(cxt))
+ ereport(ERROR,
+ (errcode(ERRCODE_JSON_SCALAR_REQUIRED),
+ errmsg(ERRMSG_JSON_SCALAR_REQUIRED),
+ errdetail("boolean lambda expression in jsonpath .%s() "
+ "returned Unknown", methodName)));
+ return jperError;
+}
+
+static JsonPathExecResult
+jspThrowSingletonRequiredError(JsonPathExecContext *cxt, const char *methodName)
+{
+ if (jspThrowErrors(cxt))
+ ereport(ERROR,
+ (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
+ errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED),
+ errdetail("lambda expression in .%s() should return singleton item",
+ methodName)));
+ return jperError;
+}
+
+static JsonPathExecResult
+jspThrowArrayNotFoundError(JsonPathExecContext *cxt, const char *methodName)
+{
+ if (jspThrowErrors(cxt))
+ ereport(ERROR,
+ (errcode(ERRCODE_JSON_ARRAY_NOT_FOUND),
+ errmsg(ERRMSG_JSON_ARRAY_NOT_FOUND),
+ errdetail("jsonpath .%s() is applied to not an array",
+ methodName)));
+
+ return jperError;
+}
+
+static JsonPathExecResult
+jspThrowWrongArgumentsError(JsonPathExecContext *cxt, int req, int given,
+ const char *methodName)
+{
+ if (jspThrowErrors(cxt))
+ ereport(ERROR,
+ (errcode(ERRCODE_JSON_SCALAR_REQUIRED),
+ errmsg(ERRMSG_JSON_SCALAR_REQUIRED),
+ errdetail("jsonpath .%s() requires %d arguments "
+ "but given %d", methodName, req, given)));
+ return jperError;
+}
+
+
+static JsonPathExecResult
+jspExecuteSingleton(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, JsonItem **result)
+{
+ JsonValueList reslist = {0};
+ JsonPathExecResult res = jspExecuteItem(cxt, jsp, jb, &reslist);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&reslist) != 1)
+ return jspThrowSingletonRequiredError(cxt, "");
+
+ *result = JsonValueListHead(&reslist);
+
+ return jperOk;
+}
+
+static JsonPathExecResult
+jspMap(JsonPathFuncContext *fcxt, bool flat)
+{
+ JsonPathExecContext *cxt = fcxt->cxt;
+ JsonItem *jb = fcxt->item;
+ JsonPathItem *func = &fcxt->args[jb ? 0 : 1];
+ void **funccache = &fcxt->argscache[jb ? 0 : 1];
+ JsonPathExecResult res;
+ JsonItem *args[3];
+ JsonItem jbvidx;
+ int index = 0;
+ int nargs = 1;
+
+ if (fcxt->nargs != (jb ? 1 : 2))
+ return jspThrowWrongArgumentsError(cxt, jb ? 1 : 2, fcxt->nargs,
+ fcxt->funcname);
+
+ if (func->type == jpiLambda && func->content.lambda.nparams > 1)
+ {
+ args[nargs++] = &jbvidx;
+ JsonItemGetType(&jbvidx) = jbvNumeric;
+ }
+
+ if (!jb)
+ {
+ JsonValueList items = {0};
+ JsonValueListIterator iter;
+ JsonItem *item;
+
+ res = jspExecuteItem(cxt, &fcxt->args[0], fcxt->jb, &items);
+
+ if (jperIsError(res))
+ return res;
+
+ JsonValueListInitIterator(&items, &iter);
+
+ while ((item = JsonValueListNext(&items, &iter)))
+ {
+ JsonValueList reslist = {0};
+
+ args[0] = item;
+
+ if (nargs > 1)
+ {
+ JsonItemNumeric(&jbvidx) = DatumGetNumeric(
+ DirectFunctionCall1(int4_numeric, Int32GetDatum(index)));
+ index++;
+ }
+
+ res = jspExecuteLambda(cxt, func, fcxt->jb, &reslist,
+ args, nargs, funccache);
+ if (jperIsError(res))
+ return res;
+
+ if (flat)
+ {
+ JsonValueListConcat(fcxt->result, reslist);
+ }
+ else
+ {
+ if (JsonValueListLength(&reslist) != 1)
+ return jspThrowSingletonRequiredError(cxt, fcxt->funcname);
+
+ JsonValueListAppend(fcxt->result, JsonValueListHead(&reslist));
+ }
+ }
+ }
+ else if (JsonbType(jb) != jbvArray)
+ {
+ JsonValueList reslist = {0};
+ JsonItemStackEntry entry;
+
+ if (!jspAutoWrap(cxt))
+ return jspThrowArrayNotFoundError(cxt, flat ? "flatmap" : "map");
+
+ args[0] = jb;
+
+ if (nargs > 1)
+ JsonItemNumeric(&jbvidx) = DatumGetNumeric(
+ DirectFunctionCall1(int4_numeric, Int32GetDatum(0)));
+
+ /* push additional stack entry for the whole item */
+ pushJsonItem(&cxt->stack, &entry, jb, &cxt->baseObject);
+ res = jspExecuteLambda(cxt, func, jb, &reslist, args, nargs, funccache);
+ popJsonItem(&cxt->stack);
+
+ if (jperIsError(res))
+ return res;
+
+ if (flat)
+ {
+ JsonValueListConcat(fcxt->result, reslist);
+ }
+ else
+ {
+ if (JsonValueListLength(&reslist) != 1)
+ return jspThrowSingletonRequiredError(cxt, fcxt->funcname);
+
+ JsonValueListAppend(fcxt->result, JsonValueListHead(&reslist));
+ }
+ }
+ else
+ {
+ JsonbValue elembuf;
+ JsonbValue *elem;
+ JsonxIterator it;
+ JsonbIteratorToken tok;
+ JsonValueList result = {0};
+ JsonItemStackEntry entry;
+ int size = JsonxArraySize(jb, cxt->isJsonb);
+ int i;
+ bool isBinary = JsonItemIsBinary(jb);
+
+ if (isBinary && size > 0)
+ {
+ elem = &elembuf;
+ JsonxIteratorInit(&it, JsonItemBinary(jb).data, cxt->isJsonb);
+ tok = JsonxIteratorNext(&it, &elembuf, false);
+ if (tok != WJB_BEGIN_ARRAY)
+ elog(ERROR, "unexpected jsonb token at the array start");
+ }
+
+ /* push additional stack entry for the whole array */
+ pushJsonItem(&cxt->stack, &entry, jb, &cxt->baseObject);
+
+ if (nargs > 1)
+ {
+ nargs = 3;
+ args[2] = jb;
+ }
+
+ for (i = 0; i < size; i++)
+ {
+ JsonValueList reslist = {0};
+ JsonItem elemjsi;
+
+ if (isBinary)
+ {
+ tok = JsonxIteratorNext(&it, elem, true);
+ if (tok != WJB_ELEM)
+ break;
+ }
+ else
+ elem = &JsonItemArray(jb).elems[i];
+
+ args[0] = JsonbValueToJsonItem(elem, &elemjsi);
+
+ if (nargs > 1)
+ {
+ JsonItemNumeric(&jbvidx) = DatumGetNumeric(
+ DirectFunctionCall1(int4_numeric, Int32GetDatum(index)));
+ index++;
+ }
+
+ res = jspExecuteLambda(cxt, func, jb, &reslist, args, nargs, funccache);
+
+ if (jperIsError(res))
+ {
+ popJsonItem(&cxt->stack);
+ return res;
+ }
+
+ if (JsonValueListLength(&reslist) != 1)
+ {
+ popJsonItem(&cxt->stack);
+ return jspThrowSingletonRequiredError(cxt, fcxt->funcname);
+ }
+
+ if (flat)
+ {
+ JsonItem *jsarr = JsonValueListHead(&reslist);
+
+ if (JsonbType(jsarr) == jbvArray)
+ {
+ if (JsonItemIsBinary(jsarr))
+ {
+ JsonxIterator it;
+ JsonbIteratorToken tok;
+ JsonbValue elem;
+
+ JsonxIteratorInit(&it, JsonItemBinary(jsarr).data, cxt->isJsonb);
+
+ while ((tok = JsonxIteratorNext(&it, &elem, true)) != WJB_DONE)
+ {
+ if (tok == WJB_ELEM)
+ {
+ JsonItem *jsi = palloc(sizeof(*jsi));
+
+ JsonValueListAppend(&result,
+ JsonbValueToJsonItem(&elem, jsi));
+ }
+ }
+ }
+ else
+ {
+ JsonbValue *elem;
+ int size = JsonItemArray(jsarr).nElems;
+ int i = 0;
+
+ for (i = 0; i < size; i++)
+ {
+ JsonItem *jsi = palloc(sizeof(*jsi));
+
+ elem = &JsonItemArray(jsarr).elems[i];
+
+ JsonValueListAppend(&result,
+ JsonbValueToJsonItem(elem, jsi));
+ }
+ }
+ }
+ else if (jspAutoWrap(cxt))
+ {
+ JsonValueListConcat(&result, reslist);
+ }
+ else
+ {
+ popJsonItem(&cxt->stack);
+ return jspThrowArrayNotFoundError(cxt, fcxt->funcname);
+ }
+ }
+ else
+ {
+ JsonValueListConcat(&result, reslist);
+ }
+ }
+
+ popJsonItem(&cxt->stack);
+
+ JsonAppendWrappedItems(fcxt->result, &result, cxt->isJsonb);
+ }
+
+ return jperOk;
+}
+
+PG_FUNCTION_INFO_V1(jsonpath_map);
+Datum
+jsonpath_map(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(jspMap((void *) PG_GETARG_POINTER(0), false));
+}
+
+PG_FUNCTION_INFO_V1(jsonpath_flatmap);
+Datum
+jsonpath_flatmap(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(jspMap((void *) PG_GETARG_POINTER(0), true));
+}
+
+typedef enum FoldType { FOLD_REDUCE, FOLD_LEFT, FOLD_RIGHT } FoldType;
+
+typedef struct FoldContext
+{
+ JsonPathExecContext *cxt;
+ JsonPathItem *func;
+ const char *funcname;
+ void **pfunccache;
+ JsonItem *item;
+ JsonItem *result;
+ JsonItem *args[4];
+ JsonItem **argres;
+ JsonItem **argelem;
+ JsonItem argidx;
+ int nargs;
+} FoldContext;
+
+static void
+foldInit(FoldContext *fcxt, JsonPathExecContext *cxt, JsonPathItem *func,
+ void **pfunccache, JsonItem *array, JsonItem *item,
+ JsonItem *result, FoldType foldtype, const char *funcname)
+{
+ fcxt->cxt = cxt;
+ fcxt->func = func;
+ fcxt->pfunccache = pfunccache;
+ fcxt->item = item;
+ fcxt->result = result;
+ fcxt->funcname = funcname;
+
+ if (foldtype == FOLD_RIGHT)
+ {
+ /* swap args for foldr() */
+ fcxt->argres = &fcxt->args[1];
+ fcxt->argelem = &fcxt->args[0];
+ }
+ else
+ {
+ fcxt->argres = &fcxt->args[0];
+ fcxt->argelem = &fcxt->args[1];
+ }
+
+ fcxt->nargs = 2;
+
+ if (func->type == jpiLambda && func->content.lambda.nparams > 2)
+ {
+ fcxt->args[fcxt->nargs++] = &fcxt->argidx;
+ JsonItemGetType(&fcxt->argidx) = jbvNumeric;
+ if (array)
+ fcxt->args[fcxt->nargs++] = array;
+ }
+}
+
+static JsonPathExecResult
+foldAccumulate(FoldContext *fcxt, JsonItem *element, int index)
+{
+ JsonValueList reslist = {0};
+ JsonPathExecResult res;
+
+ if (!fcxt->result) /* first element of reduce */
+ {
+ fcxt->result = element;
+ return jperOk;
+ }
+
+ *fcxt->argres = fcxt->result;
+ *fcxt->argelem = element;
+
+ if (fcxt->nargs > 2)
+ JsonItemNumeric(&fcxt->argidx) = DatumGetNumeric(
+ DirectFunctionCall1(int4_numeric, Int32GetDatum(index)));
+
+ res = jspExecuteLambda(fcxt->cxt, fcxt->func, fcxt->item, &reslist,
+ fcxt->args, fcxt->nargs, fcxt->pfunccache);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&reslist) != 1)
+ return jspThrowSingletonRequiredError(fcxt->cxt, fcxt->funcname);
+
+ fcxt->result = JsonValueListHead(&reslist);
+
+ return jperOk;
+}
+
+static JsonItem *
+foldDone(FoldContext *fcxt)
+{
+ return fcxt->result;
+}
+
+static void
+list_reverse(List *itemlist)
+{
+ ListCell *curlc;
+ ListCell *prevlc = NULL;
+
+ if (list_length(itemlist) <= 1)
+ return;
+
+ curlc = itemlist->head;
+ itemlist->head = itemlist->tail;
+ itemlist->tail = curlc;
+
+ while (curlc)
+ {
+ ListCell *next = curlc->next;
+
+ curlc->next = prevlc;
+ prevlc = curlc;
+ curlc = next;
+ }
+}
+
+static JsonPathExecResult
+jspFoldSeq(JsonPathFuncContext *fcxt, FoldType ftype)
+{
+ FoldContext foldcxt;
+ JsonValueList items = {0};
+ JsonItem *result = NULL;
+ JsonPathExecResult res;
+ int size;
+
+ res = jspExecuteItem(fcxt->cxt, &fcxt->args[0], fcxt->jb, &items);
+
+ if (jperIsError(res))
+ return res;
+
+ size = JsonValueListLength(&items);
+
+ if (ftype == FOLD_REDUCE)
+ {
+ if (!size)
+ return jperNotFound;
+
+ if (size == 1)
+ {
+ JsonValueListAppend(fcxt->result, JsonValueListHead(&items));
+ return jperOk;
+ }
+ }
+ else
+ {
+ res = jspExecuteSingleton(fcxt->cxt, &fcxt->args[2], fcxt->jb, &result);
+ if (jperIsError(res))
+ return res;
+
+ if (!size)
+ {
+ JsonValueListAppend(fcxt->result, result);
+ return jperOk;
+ }
+ }
+
+ foldInit(&foldcxt, fcxt->cxt, &fcxt->args[1], &fcxt->argscache[1], NULL,
+ fcxt->jb, result, ftype, fcxt->funcname);
+
+ if (ftype == FOLD_RIGHT)
+ {
+ List *itemlist = JsonValueListGetList(&items);
+ ListCell *lc;
+ int index = list_length(itemlist) - 1;
+
+ list_reverse(itemlist);
+
+ foreach(lc, itemlist)
+ {
+ res = foldAccumulate(&foldcxt, lfirst(lc), index--);
+
+ if (jperIsError(res))
+ {
+ (void) foldDone(&foldcxt);
+ return res;
+ }
+ }
+ }
+ else
+ {
+ JsonValueListIterator iter;
+ JsonItem *item;
+ int index = 0;
+
+ JsonValueListInitIterator(&items, &iter);
+
+ while ((item = JsonValueListNext(&items, &iter)))
+ {
+ res = foldAccumulate(&foldcxt, item, index++);
+
+ if (jperIsError(res))
+ {
+ (void) foldDone(&foldcxt);
+ return res;
+ }
+ }
+ }
+
+ result = foldDone(&foldcxt);
+
+ JsonValueListAppend(fcxt->result, result);
+
+ return jperOk;
+}
+
+static JsonPathExecResult
+jspFoldArray(JsonPathFuncContext *fcxt, FoldType ftype, JsonItem *item)
+{
+ JsonItem *result = NULL;
+ JsonPathExecResult res;
+ int size;
+
+ if (JsonbType(item) != jbvArray)
+ {
+ if (!jspAutoWrap(fcxt->cxt))
+ return jspThrowArrayNotFoundError(fcxt->cxt, fcxt->funcname);
+
+ if (ftype == FOLD_REDUCE)
+ {
+ JsonValueListAppend(fcxt->result, item);
+ return jperOk;
+ }
+
+ item = JsonWrapItemInArray(item, fcxt->cxt->isJsonb);
+ }
+
+ size = JsonxArraySize(item, fcxt->cxt->isJsonb);
+
+ if (ftype == FOLD_REDUCE)
+ {
+ if (!size)
+ return jperNotFound;
+ }
+ else
+ {
+ res = jspExecuteSingleton(fcxt->cxt, &fcxt->args[1], fcxt->jb, &result);
+ if (jperIsError(res))
+ return res;
+ }
+
+ if (ftype == FOLD_REDUCE && size == 1)
+ {
+ JsonbValue jbvresbuf;
+ JsonbValue *jbvres;
+
+ result = palloc(sizeof(*result));
+
+ if (JsonItemIsBinary(item))
+ {
+ jbvres = fcxt->cxt->isJsonb ?
+ getIthJsonbValueFromContainer(JsonItemBinary(item).data, 0, &jbvresbuf) :
+ getIthJsonValueFromContainer((JsonContainer *) JsonItemBinary(item).data, 0, &jbvresbuf);
+
+ if (!jbvres)
+ return jperNotFound;
+ }
+ else
+ {
+ Assert(JsonItemIsArray(item));
+ jbvres = &JsonItemArray(item).elems[0];
+ }
+
+ JsonbValueToJsonItem(jbvres, result);
+ }
+ else if (size)
+ {
+ FoldContext foldcxt;
+ JsonxIterator it;
+ JsonbIteratorToken tok;
+ JsonbValue elembuf;
+ JsonbValue *elem;
+ JsonItem itembuf;
+ int i;
+ bool foldr = ftype == FOLD_RIGHT;
+ bool useIter = false;
+
+ if (JsonItemIsBinary(item))
+ {
+ if (foldr)
+ {
+ /* unpack array for reverse iteration */
+ JsonbParseState *ps = NULL;
+ JsonbValue *jbv = (fcxt->cxt->isJsonb ? pushJsonbValue : pushJsonValue)
+ (&ps, WJB_ELEM, JsonItemJbv(item));
+
+ item = JsonbValueToJsonItem(jbv, &itembuf);
+ }
+ else
+ {
+ elem = &elembuf;
+ useIter = true;
+ JsonxIteratorInit(&it, JsonItemBinary(item).data, fcxt->cxt->isJsonb);
+ tok = JsonxIteratorNext(&it, elem, false);
+ if (tok != WJB_BEGIN_ARRAY)
+ elog(ERROR, "unexpected jsonb token at the array start");
+ }
+ }
+
+ foldInit(&foldcxt, fcxt->cxt, &fcxt->args[0], &fcxt->argscache[0],
+ item, fcxt->jb, result, ftype, fcxt->funcname);
+
+ for (i = 0; i < size; i++)
+ {
+ JsonItem elbuf;
+ JsonItem *el;
+ int index;
+
+ if (useIter)
+ {
+ tok = JsonxIteratorNext(&it, elem, true);
+ if (tok != WJB_ELEM)
+ break;
+ index = i;
+ }
+ else
+ {
+ index = foldr ? size - i - 1 : i;
+ elem = &JsonItemArray(item).elems[index];
+ }
+
+ el = JsonbValueToJsonItem(elem, &elbuf);
+
+ if (!i && ftype == FOLD_REDUCE)
+ el = copyJsonItem(el);
+
+ res = foldAccumulate(&foldcxt, el, index);
+
+ if (jperIsError(res))
+ {
+ (void) foldDone(&foldcxt);
+ return res;
+ }
+ }
+
+ result = foldDone(&foldcxt);
+ }
+
+ JsonValueListAppend(fcxt->result, result);
+
+ return jperOk;
+}
+
+static JsonPathExecResult
+jspFold(JsonPathFuncContext *fcxt, FoldType ftype, const char *funcName)
+{
+ JsonItem *item = fcxt->item;
+ int nargs = (ftype == FOLD_REDUCE ? 1 : 2) + (item ? 0 : 1);
+
+ if (fcxt->nargs != nargs)
+ return jspThrowWrongArgumentsError(fcxt->cxt, nargs, fcxt->nargs, funcName);
+
+ return item ? jspFoldArray(fcxt, ftype, item) : jspFoldSeq(fcxt, ftype);
+}
+
+PG_FUNCTION_INFO_V1(jsonpath_reduce);
+Datum
+jsonpath_reduce(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(jspFold((void *) PG_GETARG_POINTER(0), FOLD_REDUCE, "reduce"));
+}
+
+PG_FUNCTION_INFO_V1(jsonpath_fold);
+Datum
+jsonpath_fold(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(jspFold((void *) PG_GETARG_POINTER(0), FOLD_LEFT, "fold"));
+}
+
+PG_FUNCTION_INFO_V1(jsonpath_foldl);
+Datum
+jsonpath_foldl(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(jspFold((void *) PG_GETARG_POINTER(0), FOLD_LEFT, "foldl"));
+}
+
+PG_FUNCTION_INFO_V1(jsonpath_foldr);
+Datum
+jsonpath_foldr(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(jspFold((void *) PG_GETARG_POINTER(0), FOLD_RIGHT, "foldr"));
+}
+
+static JsonPathExecResult
+jspMinMax(JsonPathFuncContext *fcxt, bool max, const char *funcName)
+{
+ JsonItem *item = fcxt->item;
+ JsonItem *result = NULL;
+ JsonPathItemType cmpop = max ? jpiGreater : jpiLess;
+
+ if (fcxt->nargs != (item ? 0 : 1))
+ return jspThrowWrongArgumentsError(fcxt->cxt, item ? 0 : 1, fcxt->nargs,
+ funcName);
+
+ if (!item)
+ {
+ JsonValueList items = {0};
+ JsonValueListIterator iter;
+ JsonItem *item;
+ JsonPathExecResult res;
+
+ res = jspExecuteItem(fcxt->cxt, &fcxt->args[0], fcxt->jb, &items);
+ if (jperIsError(res))
+ return res;
+
+ if (!JsonValueListLength(&items))
+ return jperNotFound;
+
+ JsonValueListInitIterator(&items, &iter);
+
+ while ((item = JsonValueListNext(&items, &iter)))
+ {
+ if (result)
+ {
+ JsonPathBool cmp = jspCompareItems(cmpop, item, result);
+
+ if (cmp == jpbUnknown)
+ return jspThrowComparisonError(fcxt->cxt, funcName);
+
+ if (cmp == jpbTrue)
+ result = item;
+ }
+ else
+ {
+ result = item;
+ }
+ }
+ }
+ else if (JsonbType(item) != jbvArray)
+ {
+ if (!jspAutoWrap(fcxt->cxt))
+ return jspThrowArrayNotFoundError(fcxt->cxt, funcName);
+
+ result = item;
+ }
+ else
+ {
+ JsonbValue elmebuf;
+ JsonbValue *elem;
+ JsonxIterator it;
+ JsonbIteratorToken tok;
+ int size = JsonxArraySize(item, fcxt->cxt->isJsonb);
+ int i;
+ bool isBinary = JsonItemIsBinary(item);
+
+ if (isBinary)
+ {
+ elem = &elmebuf;
+ JsonxIteratorInit(&it, JsonItemBinary(item).data, fcxt->cxt->isJsonb);
+ tok = JsonxIteratorNext(&it, &elmebuf, false);
+ if (tok != WJB_BEGIN_ARRAY)
+ elog(ERROR, "unexpected jsonb token at the array start");
+ }
+
+ for (i = 0; i < size; i++)
+ {
+ JsonItem elemjsi;
+
+ if (isBinary)
+ {
+ tok = JsonxIteratorNext(&it, elem, true);
+ if (tok != WJB_ELEM)
+ break;
+ }
+ else
+ elem = &JsonItemArray(item).elems[i];
+
+ if (!i)
+ {
+ result = palloc(sizeof(*result));
+ JsonbValueToJsonItem(elem, result);
+ }
+ else
+ {
+ JsonPathBool cmp = jspCompareItems(cmpop,
+ JsonbValueToJsonItem(elem, &elemjsi),
+ result);
+
+ if (cmp == jpbUnknown)
+ return jspThrowComparisonError(fcxt->cxt, funcName);
+
+ if (cmp == jpbTrue)
+ *result = elemjsi;
+ }
+ }
+
+ if (!result)
+ return jperNotFound;
+ }
+
+ JsonValueListAppend(fcxt->result, result);
+ return jperOk;
+}
+
+PG_FUNCTION_INFO_V1(jsonpath_min);
+Datum
+jsonpath_min(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(jspMinMax((void *) PG_GETARG_POINTER(0), false, "min"));
+}
+
+PG_FUNCTION_INFO_V1(jsonpath_max);
+Datum
+jsonpath_max(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(jspMinMax((void *) PG_GETARG_POINTER(0), true, "max"));
+}
diff --git a/contrib/jsonpathx/jsonpathx.control b/contrib/jsonpathx/jsonpathx.control
new file mode 100644
index 0000000000..cc67bd05ff
--- /dev/null
+++ b/contrib/jsonpathx/jsonpathx.control
@@ -0,0 +1,5 @@
+# jsonpathx extension
+comment = 'extended JSON path item methods'
+default_version = '1.0'
+module_pathname = '$libdir/jsonpathx'
+relocatable = true
diff --git a/contrib/jsonpathx/sql/jsonpathx_json.sql b/contrib/jsonpathx/sql/jsonpathx_json.sql
new file mode 100644
index 0000000000..26f8f479f5
--- /dev/null
+++ b/contrib/jsonpathx/sql/jsonpathx_json.sql
@@ -0,0 +1,89 @@
+CREATE EXTENSION jsonpathx;
+
+-- map item method
+select json_path_query('1', 'strict $.map(x => x + 10)');
+select json_path_query('1', 'lax $.map(x => x + 10)');
+select json_path_query('[1, 2, 3]', '$.map(x => x + 10)');
+select json_path_query('[1, 2, 3]', '$.map(x => x + 10)[*]');
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.map(a => a.map(x => x + 10))');
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.flatmap(a => a.map(a => a + 10))');
+
+-- map function
+select json_path_query('1', 'strict map($, x => x + 10)');
+select json_path_query('1', 'lax map($, x => x + 10)');
+select json_path_query('[1, 2, 3]', 'map($[*], x => x + 10)');
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'map($[*], x => [map(x[*], x => x + 10)])');
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'flatmap($[*], a => map(a[*], x => x + 10))');
+
+-- reduce/fold item methods
+select json_path_query('1', 'strict $.reduce((x, y) => x + y)');
+select json_path_query('1', 'lax $.reduce((x, y) => x + y)');
+select json_path_query('1', 'strict $.fold((x, y) => x + y, 10)');
+select json_path_query('1', 'lax $.fold((x, y) => x + y, 10)');
+select json_path_query('[1, 2, 3]', '$.reduce((x, y) => x + y)');
+select json_path_query('[1, 2, 3]', '$.fold((x, y) => x + y, 100)');
+select json_path_query('[]', '$.reduce((x, y) => x + y)');
+select json_path_query('[]', '$.fold((x, y) => x + y, 100)');
+select json_path_query('[1]', '$.reduce((x, y) => x + y)');
+select json_path_query('[1, 2, 3]', '$.foldl((x, y) => [x, y], [])');
+select json_path_query('[1, 2, 3]', '$.foldr((x, y) => [y, x], [])');
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.fold((x, y) => x + y.fold((a, b) => a + b, 100), 1000)');
+
+-- reduce/fold functions
+select json_path_query('1', 'strict reduce($, (x, y) => x + y)');
+select json_path_query('1', 'lax reduce($, (x, y) => x + y)');
+select json_path_query('1', 'strict fold($, (x, y) => x + y, 10)');
+select json_path_query('1', 'lax fold($, (x, y) => x + y, 10)');
+select json_path_query('[1, 2, 3]', 'reduce($[*], (x, y) => x + y)');
+select json_path_query('[1, 2, 3]', 'fold($[*], (x, y) => x + y, 100)');
+select json_path_query('[]', 'reduce($[*], (x, y) => x + y)');
+select json_path_query('[]', 'fold($[*], (x, y) => x + y, 100)');
+select json_path_query('[1]', 'reduce($[*], (x, y) => x + y)');
+select json_path_query('[1, 2, 3]', 'foldl($[*], (x, y) => [x, y], [])');
+select json_path_query('[1, 2, 3]', 'foldr($[*], (x, y) => [y, x], [])');
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'fold($[*], (x, y) => x + y.fold((a, b) => a + b, 100), 1000)');
+
+-- min/max item methods
+select json_path_query('1', 'strict $.min()');
+select json_path_query('1', 'lax $.min()');
+select json_path_query('[]', '$.min()');
+select json_path_query('[]', '$.max()');
+select json_path_query('[null]', '$.min()');
+select json_path_query('[null]', '$.max()');
+select json_path_query('[1, 2, 3]', '$.min()');
+select json_path_query('[1, 2, 3]', '$.max()');
+select json_path_query('[2, 3, 5, null, 1, 4, null]', '$.min()');
+select json_path_query('[2, 3, 5, null, 1, 4, null]', '$.max()');
+select json_path_query('["aa", null, "a", "bbb"]', '$.min()');
+select json_path_query('["aa", null, "a", "bbb"]', '$.max()');
+select json_path_query('[1, null, "2"]', '$.max()');
+
+-- min/max functions
+select json_path_query('1', 'strict min($)');
+select json_path_query('1', 'lax min($)');
+select json_path_query('[]', 'min($[*])');
+select json_path_query('[]', 'max($[*])');
+select json_path_query('[null]', 'min($[*])');
+select json_path_query('[null]', 'max($[*])');
+select json_path_query('[1, 2, 3]', 'min($[*])');
+select json_path_query('[1, 2, 3]', 'max($[*])');
+select json_path_query('[2, 3, 5, null, 1, 4, null]', 'min($[*])');
+select json_path_query('[2, 3, 5, null, 1, 4, null]', 'max($[*])');
+select json_path_query('["aa", null, "a", "bbb"]', 'min($[*])');
+select json_path_query('["aa", null, "a", "bbb"]', 'max($[*])');
+select json_path_query('[1, null, "2"]', 'max($[*])');
+
+-- tests for simplified variable-based lambda syntax
+select json_path_query('[1, 2, 3]', '$.map($1 + 100)');
+select json_path_query('[1, 2, 3]', 'map($[*], $1 + 100)');
+select json_path_query('[1, 2, 3]', '$.reduce($1 + $2)');
+select json_path_query('[1, 2, 3]', 'reduce($[*], $1 + $2)');
+select json_path_query('[1, 2, 3]', '$.fold($1 + $2, 100)');
+select json_path_query('[1, 2, 3]', 'fold($[*], $1 + $2, 100)');
+
+-- more complex tests
+select json_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.map((x,i,a) => {n: i, sum: reduce(a[0 to i], (x,y) => x + y)})[*]');
+select json_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y,i,a) => [x[*], {n:y, s: [a[0 to i]].reduce($1+$2)}], [])[*]');
+select json_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y) => [y,y,y].map((a) => a + y).reduce((x,y)=>x+y) + x * 100, 0)');
+
+DROP EXTENSION jsonpathx;
diff --git a/contrib/jsonpathx/sql/jsonpathx_jsonb.sql b/contrib/jsonpathx/sql/jsonpathx_jsonb.sql
new file mode 100644
index 0000000000..bee7ee7833
--- /dev/null
+++ b/contrib/jsonpathx/sql/jsonpathx_jsonb.sql
@@ -0,0 +1,89 @@
+CREATE EXTENSION jsonpathx;
+
+-- map item method
+select jsonb_path_query('1', 'strict $.map(x => x + 10)');
+select jsonb_path_query('1', 'lax $.map(x => x + 10)');
+select jsonb_path_query('[1, 2, 3]', '$.map(x => x + 10)');
+select jsonb_path_query('[1, 2, 3]', '$.map(x => x + 10)[*]');
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.map(a => a.map(x => x + 10))');
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.flatmap(a => a.map(a => a + 10))');
+
+-- map function
+select jsonb_path_query('1', 'strict map($, x => x + 10)');
+select jsonb_path_query('1', 'lax map($, x => x + 10)');
+select jsonb_path_query('[1, 2, 3]', 'map($[*], x => x + 10)');
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'map($[*], x => [map(x[*], x => x + 10)])');
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'flatmap($[*], a => map(a[*], x => x + 10))');
+
+-- reduce/fold item methods
+select jsonb_path_query('1', 'strict $.reduce((x, y) => x + y)');
+select jsonb_path_query('1', 'lax $.reduce((x, y) => x + y)');
+select jsonb_path_query('1', 'strict $.fold((x, y) => x + y, 10)');
+select jsonb_path_query('1', 'lax $.fold((x, y) => x + y, 10)');
+select jsonb_path_query('[1, 2, 3]', '$.reduce((x, y) => x + y)');
+select jsonb_path_query('[1, 2, 3]', '$.fold((x, y) => x + y, 100)');
+select jsonb_path_query('[]', '$.reduce((x, y) => x + y)');
+select jsonb_path_query('[]', '$.fold((x, y) => x + y, 100)');
+select jsonb_path_query('[1]', '$.reduce((x, y) => x + y)');
+select jsonb_path_query('[1, 2, 3]', '$.foldl((x, y) => [x, y], [])');
+select jsonb_path_query('[1, 2, 3]', '$.foldr((x, y) => [y, x], [])');
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.fold((x, y) => x + y.fold((a, b) => a + b, 100), 1000)');
+
+-- reduce/fold functions
+select jsonb_path_query('1', 'strict reduce($, (x, y) => x + y)');
+select jsonb_path_query('1', 'lax reduce($, (x, y) => x + y)');
+select jsonb_path_query('1', 'strict fold($, (x, y) => x + y, 10)');
+select jsonb_path_query('1', 'lax fold($, (x, y) => x + y, 10)');
+select jsonb_path_query('[1, 2, 3]', 'reduce($[*], (x, y) => x + y)');
+select jsonb_path_query('[1, 2, 3]', 'fold($[*], (x, y) => x + y, 100)');
+select jsonb_path_query('[]', 'reduce($[*], (x, y) => x + y)');
+select jsonb_path_query('[]', 'fold($[*], (x, y) => x + y, 100)');
+select jsonb_path_query('[1]', 'reduce($[*], (x, y) => x + y)');
+select jsonb_path_query('[1, 2, 3]', 'foldl($[*], (x, y) => [x, y], [])');
+select jsonb_path_query('[1, 2, 3]', 'foldr($[*], (x, y) => [y, x], [])');
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'fold($[*], (x, y) => x + y.fold((a, b) => a + b, 100), 1000)');
+
+-- min/max item methods
+select jsonb_path_query('1', 'strict $.min()');
+select jsonb_path_query('1', 'lax $.min()');
+select jsonb_path_query('[]', '$.min()');
+select jsonb_path_query('[]', '$.max()');
+select jsonb_path_query('[null]', '$.min()');
+select jsonb_path_query('[null]', '$.max()');
+select jsonb_path_query('[1, 2, 3]', '$.min()');
+select jsonb_path_query('[1, 2, 3]', '$.max()');
+select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', '$.min()');
+select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', '$.max()');
+select jsonb_path_query('["aa", null, "a", "bbb"]', '$.min()');
+select jsonb_path_query('["aa", null, "a", "bbb"]', '$.max()');
+select jsonb_path_query('[1, null, "2"]', '$.max()');
+
+-- min/max functions
+select jsonb_path_query('1', 'strict min($)');
+select jsonb_path_query('1', 'lax min($)');
+select jsonb_path_query('[]', 'min($[*])');
+select jsonb_path_query('[]', 'max($[*])');
+select jsonb_path_query('[null]', 'min($[*])');
+select jsonb_path_query('[null]', 'max($[*])');
+select jsonb_path_query('[1, 2, 3]', 'min($[*])');
+select jsonb_path_query('[1, 2, 3]', 'max($[*])');
+select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', 'min($[*])');
+select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', 'max($[*])');
+select jsonb_path_query('["aa", null, "a", "bbb"]', 'min($[*])');
+select jsonb_path_query('["aa", null, "a", "bbb"]', 'max($[*])');
+select jsonb_path_query('[1, null, "2"]', 'max($[*])');
+
+-- tests for simplified variable-based lambda syntax
+select jsonb_path_query('[1, 2, 3]', '$.map($1 + 100)');
+select jsonb_path_query('[1, 2, 3]', 'map($[*], $1 + 100)');
+select jsonb_path_query('[1, 2, 3]', '$.reduce($1 + $2)');
+select jsonb_path_query('[1, 2, 3]', 'reduce($[*], $1 + $2)');
+select jsonb_path_query('[1, 2, 3]', '$.fold($1 + $2, 100)');
+select jsonb_path_query('[1, 2, 3]', 'fold($[*], $1 + $2, 100)');
+
+-- more complex tests
+select jsonb_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.map((x,i,a) => {n: i, sum: reduce(a[0 to i], (x,y) => x + y)})[*]');
+select jsonb_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y,i,a) => [x[*], {n:y, s: [a[0 to i]].reduce($1+$2)}], [])[*]');
+select jsonb_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y) => [y,y,y].map((a) => a + y).reduce((x,y)=>x+y) + x * 100, 0)');
+
+DROP EXTENSION jsonpathx;
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 26610b34b6..958383f51a 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -2559,11 +2559,13 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
Aggref *expr = (Aggref *) node;
APP_JUMB(expr->aggfnoid);
+ APP_JUMB(expr->aggformat);
JumbleExpr(jstate, (Node *) expr->aggdirectargs);
JumbleExpr(jstate, (Node *) expr->args);
JumbleExpr(jstate, (Node *) expr->aggorder);
JumbleExpr(jstate, (Node *) expr->aggdistinct);
JumbleExpr(jstate, (Node *) expr->aggfilter);
+ JumbleExpr(jstate, (Node *) expr->aggformatopts);
}
break;
case T_GroupingFunc:
@@ -2579,8 +2581,10 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
APP_JUMB(expr->winfnoid);
APP_JUMB(expr->winref);
+ APP_JUMB(expr->winformat);
JumbleExpr(jstate, (Node *) expr->args);
JumbleExpr(jstate, (Node *) expr->aggfilter);
+ JumbleExpr(jstate, (Node *) expr->winformatopts);
}
break;
case T_SubscriptingRef:
@@ -2598,7 +2602,9 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
FuncExpr *expr = (FuncExpr *) node;
APP_JUMB(expr->funcid);
+ APP_JUMB(expr->funcformat2);
JumbleExpr(jstate, (Node *) expr->args);
+ JumbleExpr(jstate, (Node *) expr->funcformatopts);
}
break;
case T_NamedArgExpr:
@@ -2884,6 +2890,47 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
JumbleExpr(jstate, (Node *) conf->exclRelTlist);
}
break;
+ case T_JsonValueExpr:
+ {
+ JsonValueExpr *expr = (JsonValueExpr *) node;
+
+ JumbleExpr(jstate, (Node *) expr->expr);
+ APP_JUMB(expr->format.type);
+ APP_JUMB(expr->format.encoding);
+ }
+ break;
+ case T_JsonCtorOpts:
+ {
+ JsonCtorOpts *opts = (JsonCtorOpts *) node;
+
+ APP_JUMB(opts->returning.format.type);
+ APP_JUMB(opts->returning.format.encoding);
+ APP_JUMB(opts->returning.typid);
+ APP_JUMB(opts->returning.typmod);
+ APP_JUMB(opts->unique);
+ APP_JUMB(opts->absent_on_null);
+ }
+ break;
+ case T_JsonIsPredicateOpts:
+ {
+ JsonIsPredicateOpts *opts = (JsonIsPredicateOpts *) node;
+
+ APP_JUMB(opts->unique_keys);
+ APP_JUMB(opts->value_type);
+ }
+ break;
+ case T_JsonExpr:
+ {
+ JsonExpr *jexpr = (JsonExpr *) node;
+
+ APP_JUMB(jexpr->op);
+ JumbleExpr(jstate, jexpr->raw_expr);
+ JumbleExpr(jstate, jexpr->path_spec);
+ JumbleExpr(jstate, (Node *) jexpr->passing.values);
+ JumbleExpr(jstate, jexpr->on_empty.default_expr);
+ JumbleExpr(jstate, jexpr->on_error.default_expr);
+ }
+ break;
case T_List:
foreach(temp, (List *) node)
{
@@ -2956,9 +3003,11 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
{
TableFunc *tablefunc = (TableFunc *) node;
+ APP_JUMB(tablefunc->functype);
JumbleExpr(jstate, tablefunc->docexpr);
JumbleExpr(jstate, tablefunc->rowexpr);
JumbleExpr(jstate, (Node *) tablefunc->colexprs);
+ JumbleExpr(jstate, (Node *) tablefunc->colvalexprs);
}
break;
case T_TableSampleClause:
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 6da4c834bf..019faa687e 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -2634,7 +2634,8 @@ deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context)
* If the function call came from an implicit coercion, then just show the
* first argument.
*/
- if (node->funcformat == COERCE_IMPLICIT_CAST)
+ if (node->funcformat == COERCE_IMPLICIT_CAST ||
+ node->funcformat == COERCE_INTERNAL_CAST)
{
deparseExpr((Expr *) linitial(node->args), context);
return;
@@ -2831,7 +2832,8 @@ static void
deparseRelabelType(RelabelType *node, deparse_expr_cxt *context)
{
deparseExpr(node->arg, context);
- if (node->relabelformat != COERCE_IMPLICIT_CAST)
+ if (node->relabelformat != COERCE_IMPLICIT_CAST &&
+ node->relabelformat == COERCE_INTERNAL_CAST)
appendStringInfo(context->buf, "::%s",
deparse_type_name(node->resulttype,
node->resulttypmod));
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e918133874..33ab515fc4 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6146,6 +6146,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
US
microsecond (000000-999999)
+
+ FF1
+ decisecond (0-9)
+
+
+ FF2
+ centisecond (00-99)
+
+
+ FF3
+ millisecond (000-999)
+
+
+ FF4
+ tenth of a millisecond (0000-9999)
+
+
+ FF5
+ hundredth of a millisecond (00000-99999)
+
+
+ FF6
+ microsecond (000000-999999)
+
SSSS
seconds past midnight (0-86399)
@@ -11626,9 +11650,9 @@ table2-mapping
listed in .
Each method must be preceded by a dot, while arithmetic and boolean
operators are separated from the operands by spaces. For example,
- you can get an array size:
+ you can convert a text string into a datetime value:
-'$.track.segments.size()'
+'$.track.segments[*]."start time".datetime()'
For more examples of using jsonpath operators
and methods within path expressions, see
@@ -11729,6 +11753,14 @@ table2-mapping
+
+
+
+ Enclosing the path specification into square brackets
+ [] automatically wraps the path evaluation
+ result into an array.
+
+
@@ -11922,6 +11954,27 @@ table2-mapping
$.z.abs()
0.3
+
+ datetime()
+ Datetime value converted from a string
+ ["2015-8-1", "2015-08-12"]
+ $[*] ? (@.datetime() < "2015-08-2". datetime())
+ 2015-8-1
+
+
+ datetime(template)
+ Datetime value converted from a string with a specified template
+ ["12:30", "18:40"]
+ $[*].datetime("HH24:MI")
+ "12:30:00", "18:40:00"
+
+
+ datetime(template, default_tz)
+ Datetime value converted from a string with a specified template and default timezone
+ ["12:30 -02", "18:40"]
+ $[*].datetime("HH24:MI TZH", "+03:00")
+ "12:30:00-02:00", "18:40:00+03:00"
+
keyvalue()
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ea4c85e395..54da52596d 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1287,6 +1287,64 @@ LANGUAGE INTERNAL
STRICT IMMUTABLE PARALLEL SAFE
AS 'jsonb_path_query_first';
+CREATE OR REPLACE FUNCTION
+ jsonb_path_query_first_text(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+ silent boolean DEFAULT false)
+RETURNS text
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_first_text';
+
+
+
+CREATE OR REPLACE FUNCTION
+ json_path_exists(target json, path jsonpath, vars json DEFAULT '{}',
+ silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'json_path_exists';
+
+CREATE OR REPLACE FUNCTION
+ json_path_match(target json, path jsonpath, vars json DEFAULT '{}',
+ silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'json_path_match';
+
+CREATE OR REPLACE FUNCTION
+ json_path_query(target json, path jsonpath, vars json DEFAULT '{}',
+ silent boolean DEFAULT false)
+RETURNS SETOF json
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'json_path_query';
+
+CREATE OR REPLACE FUNCTION
+ json_path_query_array(target json, path jsonpath, vars json DEFAULT '{}',
+ silent boolean DEFAULT false)
+RETURNS json
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'json_path_query_array';
+
+CREATE OR REPLACE FUNCTION
+ json_path_query_first(target json, path jsonpath, vars json DEFAULT '{}',
+ silent boolean DEFAULT false)
+RETURNS json
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'json_path_query_first';
+
+CREATE OR REPLACE FUNCTION
+ json_path_query_first_text(target json, path jsonpath, vars json DEFAULT '{}',
+ silent boolean DEFAULT false)
+RETURNS text
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'json_path_query_first_text';
+
--
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 92969636b7..b61ea629c2 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -3097,7 +3097,9 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
break;
case T_TableFuncScan:
Assert(rte->rtekind == RTE_TABLEFUNC);
- objectname = "xmltable";
+ objectname = rte->tablefunc ?
+ rte->tablefunc->functype == TFT_XMLTABLE ?
+ "xmltable" : "json_table" : NULL;
objecttag = "Table Function Name";
break;
case T_ValuesScan:
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index e4a6c20ed0..66af98979e 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -44,6 +44,7 @@
#include "pgstat.h"
#include "utils/builtins.h"
#include "utils/datum.h"
+#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
@@ -81,6 +82,40 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
int transno, int setno, int setoff, bool ishash);
+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
*
@@ -119,32 +154,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);
}
/*
@@ -156,32 +166,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);
}
/*
@@ -2106,6 +2104,124 @@ ExecInitExprRec(Expr *node, ExprState *state,
break;
}
+ case T_JsonValueExpr:
+ ExecInitExprRec(((JsonValueExpr *) node)->expr, state, resv,
+ resnull);
+ 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.raw_expr =
+ palloc(sizeof(*scratch.d.jsonexpr.raw_expr));
+
+ ExecInitExprRec((Expr *) jexpr->raw_expr, state,
+ &scratch.d.jsonexpr.raw_expr->value,
+ &scratch.d.jsonexpr.raw_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.formatted_expr =
+ ExecInitExprWithCaseValue((Expr *) jexpr->formatted_expr,
+ state->parent,
+ &scratch.d.jsonexpr.raw_expr->value,
+ &scratch.d.jsonexpr.raw_expr->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 =
+ 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);
+ Value *argname = (Value *) lfirst(argnamelc);
+ JsonPathVariableEvalContext *var = palloc(sizeof(*var));
+
+ var->name = pstrdup(argname->val.str);
+ var->typid = exprType((Node *) argexpr);
+ var->typmod = exprTypmod((Node *) argexpr);
+ var->estate = ExecInitExpr(argexpr, state->parent);
+ var->econtext = NULL;
+ var->mcxt = 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 66a67c72b2..839f3934aa 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -57,6 +57,8 @@
#include "postgres.h"
#include "access/tuptoaster.h"
+#include "access/xact.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/sequence.h"
#include "executor/execExpr.h"
@@ -64,14 +66,20 @@
#include "funcapi.h"
#include "utils/memutils.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/builtins.h"
#include "utils/date.h"
#include "utils/datum.h"
#include "utils/expandedrecord.h"
+#include "utils/jsonapi.h"
+#include "utils/jsonb.h"
+#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
+#include "utils/resowner.h"
#include "utils/timestamp.h"
#include "utils/typcache.h"
#include "utils/xml.h"
@@ -385,6 +393,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_WINDOW_FUNC,
&&CASE_EEOP_SUBPLAN,
&&CASE_EEOP_ALTERNATIVE_SUBPLAN,
+ &&CASE_EEOP_JSONEXPR,
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
&&CASE_EEOP_AGG_DESERIALIZE,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1731,7 +1740,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
{
/* too complex for an inline implementation */
ExecEvalAggOrderedTransTuple(state, op, econtext);
+ EEO_NEXT();
+ }
+ EEO_CASE(EEOP_JSONEXPR)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalJson(state, op, econtext);
EEO_NEXT();
}
@@ -4142,3 +4157,580 @@ ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op,
ExecStoreVirtualTuple(pertrans->sortslot);
tuplesort_puttupleslot(pertrans->sortstates[setno], pertrans->sortslot);
}
+
+/*
+ * Evaluate a JSON error/empty behavior result.
+ */
+static Datum
+ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
+ ExprState *default_estate, bool is_jsonb, bool *is_null)
+{
+ *is_null = false;
+
+ switch (behavior->btype)
+ {
+ case JSON_BEHAVIOR_EMPTY_ARRAY:
+ return is_jsonb
+ ? JsonbPGetDatum(JsonbMakeEmptyArray())
+ : PointerGetDatum(cstring_to_text("[]"));
+
+ case JSON_BEHAVIOR_EMPTY_OBJECT:
+ return is_jsonb
+ ? JsonbPGetDatum(JsonbMakeEmptyObject())
+ : PointerGetDatum(cstring_to_text("{}"));
+
+ case JSON_BEHAVIOR_TRUE:
+ return BoolGetDatum(true);
+
+ case JSON_BEHAVIOR_FALSE:
+ return BoolGetDatum(false);
+
+ case JSON_BEHAVIOR_NULL:
+ case JSON_BEHAVIOR_UNKNOWN:
+ case JSON_BEHAVIOR_EMPTY:
+ *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, bool isJsonb,
+ void *p, bool *error)
+{
+ ExprState *estate = p;
+
+ if (estate)
+ {
+ op->d.jsonexpr.coercion_expr->value = res;
+ op->d.jsonexpr.coercion_expr->isnull = *isNull;
+
+ return ExecEvalExpr(estate, econtext, isNull);
+ }
+ else
+ {
+ JsonExpr *jexpr = op->d.jsonexpr.jsexpr;
+ JsonCoercion *coercion = jexpr->result_coercion;
+ Jsonb *jb = *isNull || !isJsonb ? NULL : DatumGetJsonbP(res);
+ Json *js = *isNull || isJsonb ? NULL : DatumGetJsonP(res);
+
+ if ((coercion && coercion->via_io) ||
+ (jexpr->omit_quotes && !*isNull &&
+ (isJsonb ? JB_ROOT_IS_SCALAR(jb) : JsonContainerIsScalar(&js->root))))
+ {
+ /* strip quotes and call typinput function */
+ char *str = *isNull ? NULL :
+ (isJsonb ? JsonbUnquote(jb) : JsonUnquote(js));
+
+ res = InputFunctionCall(&op->d.jsonexpr.input.func, str,
+ op->d.jsonexpr.input.typioparam,
+ jexpr->returning.typmod);
+ }
+ else 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);
+ }
+ else if (coercion && coercion->via_populate)
+ res = json_populate_type(res, isJsonb ? JSONBOID : JSONOID,
+ jexpr->returning.typid,
+ jexpr->returning.typmod,
+ &op->d.jsonexpr.cache,
+ econtext->ecxt_per_query_memory,
+ isNull);
+ /* else no coercion, simply return item */
+
+ return res;
+ }
+}
+
+/*
+ * Evaluate a JSON path variable caching computed value.
+ */
+int
+EvalJsonPathVar(void *cxt, bool isJsonb, char *varName, int varNameLen,
+ JsonItem *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)
+ {
+ MemoryContext oldcxt = var->mcxt ?
+ MemoryContextSwitchTo(var->mcxt) : NULL;
+
+ var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
+ var->evaluated = true;
+
+ if (oldcxt)
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ if (var->isnull)
+ {
+ JsonItemGetType(val) = jbvNull;
+ return 0;
+ }
+
+ JsonItemFromDatum(var->value, var->typid, var->typmod, val, isJsonb);
+
+ *baseObject = *JsonItemJbv(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(JsonItem *item, bool is_jsonb,
+ JsonReturning *returning,
+ struct JsonCoercionsState *coercions,
+ struct JsonCoercionState **pcoercion)
+{
+ struct JsonCoercionState *coercion;
+ Datum res;
+ JsonItem buf;
+
+ if (JsonItemIsBinary(item) &&
+ (is_jsonb ?
+ JsonbExtractScalar(JsonItemBinary(item).data, JsonItemJbv(&buf)) :
+ JsonExtractScalar((JsonContainer *) JsonItemBinary(item).data,
+ JsonItemJbv(&buf))))
+ item = &buf;
+
+ /* get coercion state reference and datum of the corresponding SQL type */
+ switch (JsonItemGetType(item))
+ {
+ case jbvNull:
+ coercion = &coercions->null;
+ res = (Datum) 0;
+ break;
+
+ case jbvString:
+ coercion = &coercions->string;
+ res = PointerGetDatum(
+ cstring_to_text_with_len(JsonItemString(item).val,
+ JsonItemString(item).len));
+ break;
+
+ case jbvNumeric:
+ coercion = &coercions->numeric;
+ res = JsonItemNumericDatum(item);
+ break;
+
+ case jsiDouble:
+ coercion = &coercions->dbl;
+ res = JsonItemDoubleDatum(item);
+ break;
+
+ case jbvBool:
+ coercion = &coercions->boolean;
+ res = JsonItemBool(item);
+ break;
+
+ case jsiDatetime:
+ res = JsonItemDatetime(item).value;
+ switch (JsonItemDatetime(item).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",
+ JsonItemDatetime(item).typid);
+ return (Datum) 0;
+ }
+ break;
+
+ case jbvArray:
+ case jbvObject:
+ case jbvBinary:
+ coercion = &coercions->composite;
+ res = JsonItemToJsonxDatum(item, is_jsonb);
+ break;
+
+ default:
+ elog(ERROR, "unexpected jsonb value type %d", JsonItemGetType(item));
+ return (Datum) 0;
+ }
+
+ *pcoercion = coercion;
+
+ return res;
+}
+
+typedef Datum (*JsonFunc)(ExprEvalStep *op, ExprContext *econtext,
+ Datum item, bool *resnull, bool isjsonb,
+ void *p, bool *error);
+
+static Datum
+ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,
+ ExprContext *econtext,
+ Datum res, bool *resnull, bool isjsonb,
+ void *p, bool *error, bool subtrans)
+{
+ if (subtrans)
+ {
+ /*
+ * We should catch exceptions of category ERRCODE_DATA_EXCEPTION
+ * and execute the corresponding ON ERROR behavior then.
+ */
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner 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, isjsonb, 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;
+ }
+ else
+ {
+ /* No need to use subtransactions. */
+ //Assert(!error);
+ return func(op, econtext, res, resnull, isjsonb, p, error);
+ }
+}
+
+
+typedef struct
+{
+ JsonPath *path;
+ bool *error;
+ bool coercionInSubtrans;
+} ExecEvalJsonExprContext;
+
+static Datum
+ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
+ Datum item, bool *resnull, bool isjsonb, void *pcxt,
+ bool *error)
+{
+ ExecEvalJsonExprContext *cxt = pcxt;
+ //bool *error = cxt->error;
+ JsonPath *path = cxt->path;
+ JsonExpr *jexpr = op->d.jsonexpr.jsexpr;
+ ExprState *estate = NULL;
+ bool throwErrors = !!error;//expr->on_error.btype == JSON_BEHAVIOR_ERROR;
+ bool empty = false;
+ Datum res = (Datum) 0;
+
+ if (op->d.jsonexpr.formatted_expr)
+ {
+ bool isnull;
+
+ Assert(!cxt->coercionInSubtrans);
+
+ op->d.jsonexpr.raw_expr->value = item;
+ op->d.jsonexpr.raw_expr->isnull = false;
+
+ item = ExecEvalExpr(op->d.jsonexpr.formatted_expr, econtext, &isnull);
+ if (isnull)
+ {
+ /* execute domain checks for NULLs */
+ (void) ExecEvalJsonExprCoercion(op, econtext, res, resnull,
+ isjsonb, NULL, NULL);
+ *resnull = true;
+ return (Datum) 0;
+ }
+ }
+
+ switch (jexpr->op)
+ {
+ case IS_JSON_QUERY:
+ res = JsonPathQuery(item, path, jexpr->wrapper,
+ &empty, error,
+ op->d.jsonexpr.args, isjsonb);
+ *resnull = !DatumGetPointer(res);
+ if (error && *error)
+ return (Datum) 0;
+ break;
+
+ case IS_JSON_VALUE:
+ {
+ struct JsonCoercionState *jcstate;
+ JsonItem *jbv = JsonPathValue(item, path, &empty, error,
+ op->d.jsonexpr.args, isjsonb);
+
+ 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 = JsonItemToJsonxDatum(jbv, isjsonb);
+ break;
+ }
+
+ /* Use coercion from SQL/JSON item type to the output type */
+ res = ExecPrepareJsonItemCoercion(jbv, isjsonb,
+ &op->d.jsonexpr.jsexpr->returning,
+ &op->d.jsonexpr.coercions,
+ &jcstate);
+
+ if (jcstate->coercion &&
+ (jcstate->coercion->via_io ||
+ jcstate->coercion->via_populate))
+ {
+ if (throwErrors)
+ {
+ *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_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE
+ * but does not define its number.
+ */
+ (errcode(ERRCODE_JSON_SCALAR_REQUIRED),
+ errmsg("SQL/JSON item cannot be cast to target type")));
+ }
+ else if (jcstate->estate)
+ {
+ estate = jcstate->estate; /* coerce using expression */
+ break;
+ }
+ /* else no coercion */
+
+ return res;
+ }
+
+ case IS_JSON_EXISTS:
+ {
+ bool res = JsonPathExists(item, path,
+ op->d.jsonexpr.args,
+ isjsonb, error);
+
+ *resnull = error && *error;
+ return BoolGetDatum(res);
+ }
+
+ case IS_JSON_TABLE:
+ *resnull = false;
+ return item;
+
+ default:
+ elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
+ return (Datum) 0;
+ }
+
+ if (empty)
+ {
+ if (jexpr->on_empty.btype == JSON_BEHAVIOR_ERROR)
+ {
+ if (throwErrors)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_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,
+ isjsonb, resnull);
+ }
+
+ return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
+ res, resnull, isjsonb, estate, error,
+ cxt->coercionInSubtrans);
+}
+
+bool
+ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
+ struct JsonCoercionsState *coercions)
+{
+ if (jsexpr->on_error.btype == JSON_BEHAVIOR_ERROR)
+ return false;
+
+ if (jsexpr->formatted_expr)
+ return true;
+
+ if (jsexpr->op == IS_JSON_EXISTS)
+ 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;
+ Oid formattedType = exprType(jexpr->formatted_expr ?
+ jexpr->formatted_expr :
+ jexpr->raw_expr);
+ bool isjsonb = formattedType == JSONBOID;
+ 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.raw_expr->isnull || op->d.jsonexpr.pathspec->isnull)
+ {
+ /* execute domain checks for NULLs */
+ (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull,
+ isjsonb, NULL, NULL);
+
+ Assert(*op->resnull);
+ *op->resnull = true;
+
+ return;
+ }
+
+ item = op->d.jsonexpr.raw_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, isjsonb, &cxt, cxt.error,
+ needSubtrans);
+
+ if (error)
+ {
+ /* Execute ON ERROR behavior */
+ res = ExecEvalJsonBehavior(econtext, &jexpr->on_error,
+ op->d.jsonexpr.default_on_error,
+ isjsonb, op->resnull);
+
+ if (jexpr->op != IS_JSON_EXISTS &&
+ /* result is already coerced in DEFAULT behavior case */
+ jexpr->on_error.btype != JSON_BEHAVIOR_DEFAULT)
+ res = ExecEvalJsonExprCoercion(op, econtext, res,
+ op->resnull, isjsonb,
+ NULL, NULL);
+ }
+
+ *op->resvalue = res;
+}
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c
index 45d5f3c424..98adecdcf4 100644
--- a/src/backend/executor/nodeTableFuncscan.c
+++ b/src/backend/executor/nodeTableFuncscan.c
@@ -23,11 +23,14 @@
#include "postgres.h"
#include "nodes/execnodes.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeTableFuncscan.h"
#include "executor/tablefunc.h"
#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
#include "utils/builtins.h"
+#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/xml.h"
@@ -162,8 +165,10 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
scanstate->ss.ps.qual =
ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps);
- /* Only XMLTABLE is supported currently */
- scanstate->routine = &XmlTableRoutine;
+ /* Only XMLTABLE and JSON_TABLE are supported currently */
+ scanstate->routine =
+ tf->functype == TFT_XMLTABLE ? &XmlTableRoutine :
+ exprType(tf->docexpr) == JSONBOID ? &JsonbTableRoutine : &JsonTableRoutine;
scanstate->perTableCxt =
AllocSetContextCreate(CurrentMemoryContext,
@@ -384,14 +389,17 @@ tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc)
routine->SetNamespace(tstate, ns_name, ns_uri);
}
- /* Install the row filter expression into the table builder context */
- value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull);
- if (isnull)
- ereport(ERROR,
- (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("row filter expression must not be null")));
+ if (routine->SetRowFilter)
+ {
+ /* Install the row filter expression into the table builder context */
+ value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("row filter expression must not be null")));
- routine->SetRowFilter(tstate, TextDatumGetCString(value));
+ routine->SetRowFilter(tstate, TextDatumGetCString(value));
+ }
/*
* Install the column filter expressions into the table builder context.
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 30133634c7..c925f8eb9a 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2431,6 +2431,13 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[i + 1]);
break;
+
+ case EEOP_JSONEXPR:
+ build_EvalXFunc(b, mod, "ExecEvalJson",
+ v_state, v_econtext, op);
+ LLVMBuildBr(b, opblocks[i + 1]);
+ break;
+
case EEOP_LAST:
Assert(false);
break;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 78deade89b..3f3ad971df 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1302,6 +1302,7 @@ _copyTableFunc(const TableFunc *from)
{
TableFunc *newnode = makeNode(TableFunc);
+ COPY_SCALAR_FIELD(functype);
COPY_NODE_FIELD(ns_uris);
COPY_NODE_FIELD(ns_names);
COPY_NODE_FIELD(docexpr);
@@ -1312,7 +1313,9 @@ _copyTableFunc(const TableFunc *from)
COPY_NODE_FIELD(colcollations);
COPY_NODE_FIELD(colexprs);
COPY_NODE_FIELD(coldefexprs);
+ COPY_NODE_FIELD(colvalexprs);
COPY_BITMAPSET_FIELD(notnulls);
+ COPY_NODE_FIELD(plan);
COPY_SCALAR_FIELD(ordinalitycol);
COPY_LOCATION_FIELD(location);
@@ -1447,6 +1450,8 @@ _copyAggref(const Aggref *from)
COPY_SCALAR_FIELD(aggkind);
COPY_SCALAR_FIELD(agglevelsup);
COPY_SCALAR_FIELD(aggsplit);
+ COPY_SCALAR_FIELD(aggformat);
+ COPY_NODE_FIELD(aggformatopts);
COPY_LOCATION_FIELD(location);
return newnode;
@@ -1486,6 +1491,8 @@ _copyWindowFunc(const WindowFunc *from)
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(winstar);
COPY_SCALAR_FIELD(winagg);
+ COPY_SCALAR_FIELD(winformat);
+ COPY_NODE_FIELD(winformatopts);
COPY_LOCATION_FIELD(location);
return newnode;
@@ -1527,6 +1534,8 @@ _copyFuncExpr(const FuncExpr *from)
COPY_SCALAR_FIELD(funccollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
+ COPY_SCALAR_FIELD(funcformat2);
+ COPY_NODE_FIELD(funcformatopts);
COPY_LOCATION_FIELD(location);
return newnode;
@@ -2201,6 +2210,412 @@ _copyOnConflictExpr(const OnConflictExpr *from)
return newnode;
}
+/*
+ * _copyJsonValueExpr
+ */
+static JsonValueExpr *
+_copyJsonValueExpr(const JsonValueExpr *from)
+{
+ JsonValueExpr *newnode = makeNode(JsonValueExpr);
+
+ COPY_NODE_FIELD(expr);
+ COPY_SCALAR_FIELD(format);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonKeyValue
+ */
+static JsonKeyValue *
+_copyJsonKeyValue(const JsonKeyValue *from)
+{
+ JsonKeyValue *newnode = makeNode(JsonKeyValue);
+
+ COPY_NODE_FIELD(key);
+ COPY_NODE_FIELD(value);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonObjectCtor
+ */
+static JsonObjectCtor *
+_copyJsonObjectCtor(const JsonObjectCtor *from)
+{
+ JsonObjectCtor *newnode = makeNode(JsonObjectCtor);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_NODE_FIELD(output);
+ COPY_SCALAR_FIELD(absent_on_null);
+ COPY_SCALAR_FIELD(unique);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonCtorOpts
+ */
+static JsonCtorOpts *
+_copyJsonCtorOpts(const JsonCtorOpts *from)
+{
+ JsonCtorOpts *newnode = makeNode(JsonCtorOpts);
+
+ COPY_SCALAR_FIELD(returning.format.type);
+ COPY_SCALAR_FIELD(returning.format.encoding);
+ COPY_LOCATION_FIELD(returning.format.location);
+ COPY_SCALAR_FIELD(returning.typid);
+ COPY_SCALAR_FIELD(returning.typmod);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(absent_on_null);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonObjectAgg
+ */
+static JsonObjectAgg *
+_copyJsonObjectAgg(const JsonObjectAgg *from)
+{
+ JsonObjectAgg *newnode = makeNode(JsonObjectAgg);
+
+ COPY_NODE_FIELD(ctor.output);
+ COPY_NODE_FIELD(ctor.agg_filter);
+ COPY_NODE_FIELD(ctor.agg_order);
+ COPY_NODE_FIELD(ctor.over);
+ COPY_LOCATION_FIELD(ctor.location);
+ 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_SCALAR_FIELD(returning);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonArrayCtor
+ */
+static JsonArrayCtor *
+_copyJsonArrayCtor(const JsonArrayCtor *from)
+{
+ JsonArrayCtor *newnode = makeNode(JsonArrayCtor);
+
+ 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(ctor.output);
+ COPY_NODE_FIELD(ctor.agg_filter);
+ COPY_NODE_FIELD(ctor.agg_order);
+ COPY_NODE_FIELD(ctor.over);
+ COPY_LOCATION_FIELD(ctor.location);
+ COPY_NODE_FIELD(arg);
+ COPY_SCALAR_FIELD(absent_on_null);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonArrayQueryCtor
+ */
+static JsonArrayQueryCtor *
+_copyJsonArrayQueryCtor(const JsonArrayQueryCtor *from)
+{
+ JsonArrayQueryCtor *newnode = makeNode(JsonArrayQueryCtor);
+
+ COPY_NODE_FIELD(query);
+ COPY_NODE_FIELD(output);
+ COPY_SCALAR_FIELD(format);
+ COPY_SCALAR_FIELD(absent_on_null);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonExpr
+ */
+static JsonExpr *
+_copyJsonExpr(const JsonExpr *from)
+{
+ JsonExpr *newnode = makeNode(JsonExpr);
+
+ COPY_SCALAR_FIELD(op);
+ COPY_NODE_FIELD(raw_expr);
+ COPY_NODE_FIELD(formatted_expr);
+ COPY_NODE_FIELD(result_coercion);
+ COPY_SCALAR_FIELD(format);
+ COPY_NODE_FIELD(path_spec);
+ COPY_NODE_FIELD(passing.values);
+ COPY_NODE_FIELD(passing.names);
+ COPY_SCALAR_FIELD(returning);
+ COPY_SCALAR_FIELD(on_error);
+ COPY_NODE_FIELD(on_error.default_expr);
+ COPY_SCALAR_FIELD(on_empty);
+ COPY_NODE_FIELD(on_empty.default_expr);
+ 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;
+}
+
+/*
+ * _copylJsonItemCoercions
+ */
+static JsonItemCoercions *
+_copyJsonItemCoercions(const JsonItemCoercions *from)
+{
+ JsonItemCoercions *newnode = makeNode(JsonItemCoercions);
+
+ COPY_NODE_FIELD(null);
+ COPY_NODE_FIELD(string);
+ COPY_NODE_FIELD(numeric);
+ COPY_NODE_FIELD(boolean);
+ COPY_NODE_FIELD(date);
+ COPY_NODE_FIELD(time);
+ COPY_NODE_FIELD(timetz);
+ COPY_NODE_FIELD(timestamp);
+ COPY_NODE_FIELD(timestamptz);
+ COPY_NODE_FIELD(composite);
+
+ return newnode;
+}
+
+
+/*
+ * _copyJsonFuncExpr
+ */
+static JsonFuncExpr *
+_copyJsonFuncExpr(const JsonFuncExpr *from)
+{
+ JsonFuncExpr *newnode = makeNode(JsonFuncExpr);
+
+ COPY_SCALAR_FIELD(op);
+ COPY_NODE_FIELD(common);
+ COPY_NODE_FIELD(output);
+ COPY_NODE_FIELD(on_empty);
+ COPY_NODE_FIELD(on_error);
+ COPY_SCALAR_FIELD(wrapper);
+ COPY_SCALAR_FIELD(omit_quotes);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonIsPredicate
+ */
+static JsonIsPredicate *
+_copyJsonIsPredicate(const JsonIsPredicate *from)
+{
+ JsonIsPredicate *newnode = makeNode(JsonIsPredicate);
+
+ COPY_NODE_FIELD(expr);
+ COPY_SCALAR_FIELD(format);
+ COPY_SCALAR_FIELD(vtype);
+ COPY_SCALAR_FIELD(unique_keys);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonIsPredicateOpts
+ */
+static JsonIsPredicateOpts *
+_copyJsonIsPredicateOpts(const JsonIsPredicateOpts *from)
+{
+ JsonIsPredicateOpts *newnode = makeNode(JsonIsPredicateOpts);
+
+ COPY_SCALAR_FIELD(value_type);
+ COPY_SCALAR_FIELD(unique_keys);
+
+ 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;
+}
+
+/*
+ * _copyJsonTable
+ */
+static JsonTable *
+_copyJsonTable(const JsonTable *from)
+{
+ JsonTable *newnode = makeNode(JsonTable);
+
+ COPY_NODE_FIELD(common);
+ COPY_NODE_FIELD(columns);
+ COPY_NODE_FIELD(plan);
+ COPY_NODE_FIELD(on_error);
+ COPY_NODE_FIELD(alias);
+ COPY_SCALAR_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonTableColumn
+ */
+static JsonTableColumn *
+_copyJsonTableColumn(const JsonTableColumn *from)
+{
+ JsonTableColumn *newnode = makeNode(JsonTableColumn);
+
+ COPY_SCALAR_FIELD(coltype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(typeName);
+ COPY_STRING_FIELD(pathspec);
+ COPY_STRING_FIELD(pathname);
+ COPY_SCALAR_FIELD(format);
+ COPY_SCALAR_FIELD(wrapper);
+ COPY_SCALAR_FIELD(omit_quotes);
+ COPY_NODE_FIELD(columns);
+ COPY_NODE_FIELD(on_empty);
+ COPY_NODE_FIELD(on_error);
+ COPY_SCALAR_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonTablePlan
+ */
+static JsonTablePlan *
+_copyJsonTablePlan(const JsonTablePlan *from)
+{
+ JsonTablePlan *newnode = makeNode(JsonTablePlan);
+
+ COPY_SCALAR_FIELD(plan_type);
+ COPY_SCALAR_FIELD(join_type);
+ COPY_STRING_FIELD(pathname);
+ COPY_NODE_FIELD(plan1);
+ COPY_NODE_FIELD(plan2);
+ COPY_SCALAR_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonTableParentNode
+ */
+static JsonTableParentNode *
+_copyJsonTableParentNode(const JsonTableParentNode *from)
+{
+ JsonTableParentNode *newnode = makeNode(JsonTableParentNode);
+
+ COPY_NODE_FIELD(path);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(child);
+ COPY_SCALAR_FIELD(outerJoin);
+ COPY_SCALAR_FIELD(colMin);
+ COPY_SCALAR_FIELD(colMax);
+
+ return newnode;
+}
+
+/*
+ * _copyJsonTableSiblingNode
+ */
+static JsonTableSiblingNode *
+_copyJsonTableSiblingNode(const JsonTableSiblingNode *from)
+{
+ JsonTableSiblingNode *newnode = makeNode(JsonTableSiblingNode);
+
+ COPY_NODE_FIELD(larg);
+ COPY_NODE_FIELD(rarg);
+ COPY_SCALAR_FIELD(cross);
+
+ return newnode;
+}
+
/* ****************************************************************
* pathnodes.h copy functions
*
@@ -5102,6 +5517,75 @@ copyObjectImpl(const void *from)
case T_OnConflictExpr:
retval = _copyOnConflictExpr(from);
break;
+ case T_JsonValueExpr:
+ retval = _copyJsonValueExpr(from);
+ break;
+ case T_JsonKeyValue:
+ retval = _copyJsonKeyValue(from);
+ break;
+ case T_JsonCtorOpts:
+ retval = _copyJsonCtorOpts(from);
+ break;
+ case T_JsonObjectCtor:
+ retval = _copyJsonObjectCtor(from);
+ break;
+ case T_JsonObjectAgg:
+ retval = _copyJsonObjectAgg(from);
+ break;
+ case T_JsonOutput:
+ retval = _copyJsonOutput(from);
+ break;
+ case T_JsonArrayCtor:
+ retval = _copyJsonArrayCtor(from);
+ break;
+ case T_JsonArrayQueryCtor:
+ retval = _copyJsonArrayQueryCtor(from);
+ break;
+ case T_JsonArrayAgg:
+ retval = _copyJsonArrayAgg(from);
+ break;
+ case T_JsonIsPredicate:
+ retval = _copyJsonIsPredicate(from);
+ break;
+ case T_JsonIsPredicateOpts:
+ retval = _copyJsonIsPredicateOpts(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;
+ case T_JsonTable:
+ retval = _copyJsonTable(from);
+ break;
+ case T_JsonTableColumn:
+ retval = _copyJsonTableColumn(from);
+ break;
+ case T_JsonTablePlan:
+ retval = _copyJsonTablePlan(from);
+ break;
+ case T_JsonTableParentNode:
+ retval = _copyJsonTableParentNode(from);
+ break;
+ case T_JsonTableSiblingNode:
+ retval = _copyJsonTableSiblingNode(from);
+ break;
/*
* RELATION NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4f2ebe5118..87c76ee4d7 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -120,6 +120,7 @@ _equalRangeVar(const RangeVar *a, const RangeVar *b)
static bool
_equalTableFunc(const TableFunc *a, const TableFunc *b)
{
+ COMPARE_SCALAR_FIELD(functype);
COMPARE_NODE_FIELD(ns_uris);
COMPARE_NODE_FIELD(ns_names);
COMPARE_NODE_FIELD(docexpr);
@@ -130,13 +131,38 @@ _equalTableFunc(const TableFunc *a, const TableFunc *b)
COMPARE_NODE_FIELD(colcollations);
COMPARE_NODE_FIELD(colexprs);
COMPARE_NODE_FIELD(coldefexprs);
+ COMPARE_NODE_FIELD(colvalexprs);
COMPARE_BITMAPSET_FIELD(notnulls);
+ COMPARE_NODE_FIELD(plan);
COMPARE_SCALAR_FIELD(ordinalitycol);
COMPARE_LOCATION_FIELD(location);
return true;
}
+static bool
+_equalJsonTableParentNode(const JsonTableParentNode *a, const JsonTableParentNode *b)
+{
+ COMPARE_NODE_FIELD(path);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(child);
+ COMPARE_SCALAR_FIELD(outerJoin);
+ COMPARE_SCALAR_FIELD(colMin);
+ COMPARE_SCALAR_FIELD(colMax);
+
+ return true;
+}
+
+static bool
+_equalJsonTableSiblingNode(const JsonTableSiblingNode *a, const JsonTableSiblingNode *b)
+{
+ COMPARE_NODE_FIELD(larg);
+ COMPARE_NODE_FIELD(rarg);
+ COMPARE_SCALAR_FIELD(cross);
+
+ return true;
+}
+
static bool
_equalIntoClause(const IntoClause *a, const IntoClause *b)
{
@@ -228,6 +254,8 @@ _equalAggref(const Aggref *a, const Aggref *b)
COMPARE_SCALAR_FIELD(aggkind);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_SCALAR_FIELD(aggsplit);
+ COMPARE_SCALAR_FIELD(aggformat);
+ COMPARE_NODE_FIELD(aggformatopts);
COMPARE_LOCATION_FIELD(location);
return true;
@@ -260,6 +288,8 @@ _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(winstar);
COMPARE_SCALAR_FIELD(winagg);
+ COMPARE_SCALAR_FIELD(winformat);
+ COMPARE_NODE_FIELD(winformatopts);
COMPARE_LOCATION_FIELD(location);
return true;
@@ -291,6 +321,8 @@ _equalFuncExpr(const FuncExpr *a, const FuncExpr *b)
COMPARE_SCALAR_FIELD(funccollid);
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_NODE_FIELD(args);
+ COMPARE_SCALAR_FIELD(funcformat2);
+ COMPARE_NODE_FIELD(funcformatopts);
COMPARE_LOCATION_FIELD(location);
return true;
@@ -814,6 +846,108 @@ _equalOnConflictExpr(const OnConflictExpr *a, const OnConflictExpr *b)
return true;
}
+static bool
+_equalJsonValueExpr(const JsonValueExpr *a, const JsonValueExpr *b)
+{
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_SCALAR_FIELD(format.type);
+ COMPARE_SCALAR_FIELD(format.encoding);
+ COMPARE_LOCATION_FIELD(format.location);
+
+ return true;
+}
+
+static bool
+_equalJsonCtorOpts(const JsonCtorOpts *a, const JsonCtorOpts *b)
+{
+ COMPARE_SCALAR_FIELD(returning.format.type);
+ COMPARE_SCALAR_FIELD(returning.format.encoding);
+ COMPARE_LOCATION_FIELD(returning.format.location);
+ COMPARE_SCALAR_FIELD(returning.typid);
+ COMPARE_SCALAR_FIELD(returning.typmod);
+ COMPARE_SCALAR_FIELD(absent_on_null);
+ COMPARE_SCALAR_FIELD(unique);
+
+ return true;
+}
+
+static bool
+_equalJsonIsPredicateOpts(const JsonIsPredicateOpts *a,
+ const JsonIsPredicateOpts *b)
+{
+ COMPARE_SCALAR_FIELD(value_type);
+ COMPARE_SCALAR_FIELD(unique_keys);
+
+ return true;
+}
+
+/*
+ * _equalJsonExpr
+ */
+static bool
+_equalJsonExpr(const JsonExpr *a, const JsonExpr *b)
+{
+ COMPARE_SCALAR_FIELD(op);
+ COMPARE_NODE_FIELD(raw_expr);
+ COMPARE_NODE_FIELD(formatted_expr);
+ COMPARE_NODE_FIELD(result_coercion);
+ COMPARE_SCALAR_FIELD(format.type);
+ COMPARE_SCALAR_FIELD(format.encoding);
+ COMPARE_LOCATION_FIELD(format.location);
+ COMPARE_NODE_FIELD(path_spec);
+ COMPARE_NODE_FIELD(passing.values);
+ COMPARE_NODE_FIELD(passing.names);
+ COMPARE_SCALAR_FIELD(returning.format.type);
+ COMPARE_SCALAR_FIELD(returning.format.encoding);
+ COMPARE_LOCATION_FIELD(returning.format.location);
+ COMPARE_SCALAR_FIELD(returning.typid);
+ COMPARE_SCALAR_FIELD(returning.typmod);
+ COMPARE_SCALAR_FIELD(on_error.btype);
+ COMPARE_NODE_FIELD(on_error.default_expr);
+ COMPARE_SCALAR_FIELD(on_empty.btype);
+ COMPARE_NODE_FIELD(on_empty.default_expr);
+ 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
*/
@@ -3175,6 +3309,30 @@ equal(const void *a, const void *b)
case T_JoinExpr:
retval = _equalJoinExpr(a, b);
break;
+ case T_JsonValueExpr:
+ retval = _equalJsonValueExpr(a, b);
+ break;
+ case T_JsonCtorOpts:
+ retval = _equalJsonCtorOpts(a, b);
+ break;
+ case T_JsonIsPredicateOpts:
+ retval = _equalJsonIsPredicateOpts(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;
+ case T_JsonTableParentNode:
+ retval = _equalJsonTableParentNode(a, b);
+ break;
+ case T_JsonTableSiblingNode:
+ retval = _equalJsonTableSiblingNode(a, b);
+ break;
/*
* RELATION NODES
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 7085ed2c4c..e96e9f0154 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -20,6 +20,7 @@
#include "fmgr.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "utils/errcodes.h"
#include "utils/lsyscache.h"
@@ -762,3 +763,106 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+/*
+ * makeJsonValueExpr -
+ * creates a JsonValueExpr node
+ */
+JsonValueExpr *
+makeJsonValueExpr(Expr *expr, JsonFormat format)
+{
+ JsonValueExpr *jve = makeNode(JsonValueExpr);
+
+ jve->expr = expr;
+ jve->format = format;
+
+ return jve;
+}
+
+/*
+ * makeJsonBehavior -
+ * creates a JsonBehavior node
+ */
+JsonBehavior *
+makeJsonBehavior(JsonBehaviorType type, Node *default_expr)
+{
+ JsonBehavior *behavior = makeNode(JsonBehavior);
+
+ behavior->btype = type;
+ behavior->default_expr = default_expr;
+
+ return behavior;
+}
+
+/*
+ * makeJsonTableJoinedPlan -
+ * creates a joined JsonTablePlan node
+ */
+Node *
+makeJsonTableJoinedPlan(JsonTablePlanJoinType type, Node *plan1, Node *plan2,
+ int location)
+{
+ JsonTablePlan *n = makeNode(JsonTablePlan);
+
+ n->plan_type = JSTP_JOINED;
+ n->join_type = type;
+ n->plan1 = castNode(JsonTablePlan, plan1);
+ n->plan2 = castNode(JsonTablePlan, plan2);
+ n->location = location;
+
+ return (Node *) n;
+}
+
+/*
+ * makeJsonEncoding -
+ * converts JSON encoding name to enum JsonEncoding
+ */
+JsonEncoding
+makeJsonEncoding(char *name)
+{
+ if (!pg_strcasecmp(name, "utf8"))
+ return JS_ENC_UTF8;
+ if (!pg_strcasecmp(name, "utf16"))
+ return JS_ENC_UTF16;
+ if (!pg_strcasecmp(name, "utf32"))
+ return JS_ENC_UTF32;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized JSON encoding: %s", name)));
+
+ return JS_ENC_DEFAULT;
+}
+
+/*
+ * makeJsonKeyValue -
+ * creates a JsonKeyValue node
+ */
+Node *
+makeJsonKeyValue(Node *key, Node *value)
+{
+ JsonKeyValue *n = makeNode(JsonKeyValue);
+
+ n->key = (Expr *) key;
+ n->value = castNode(JsonValueExpr, value);
+
+ return (Node *) n;
+}
+
+/*
+ * makeJsonIsPredicate -
+ * creates a JsonIsPredicate node
+ */
+Node *
+makeJsonIsPredicate(Node *expr, JsonFormat format, JsonValueType vtype,
+ bool unique_keys)
+{
+ JsonIsPredicate *n = makeNode(JsonIsPredicate);
+
+ n->expr = expr;
+ n->format = format;
+ n->vtype = vtype;
+ n->unique_keys = unique_keys;
+
+ return (Node *) n;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 05ae73f7db..4c32225e4b 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -259,6 +259,15 @@ exprType(const Node *expr)
case T_PlaceHolderVar:
type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
break;
+ case T_JsonValueExpr:
+ type = exprType((Node *) ((const JsonValueExpr *) expr)->expr);
+ 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 */
@@ -492,6 +501,12 @@ 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)->expr);
+ case T_JsonExpr:
+ return ((JsonExpr *) expr)->returning.typmod;
+ case T_JsonCoercion:
+ return exprTypmod(((const JsonCoercion *) expr)->expr);
default:
break;
}
@@ -907,6 +922,24 @@ exprCollation(const Node *expr)
case T_PlaceHolderVar:
coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
break;
+ case T_JsonValueExpr:
+ coll = exprCollation((Node *) ((const JsonValueExpr *) expr)->expr);
+ 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 */
@@ -1110,6 +1143,25 @@ exprSetCollation(Node *expr, Oid collation)
Assert(!OidIsValid(collation)); /* result is always an integer
* type */
break;
+ case T_JsonValueExpr:
+ exprSetCollation((Node *) ((const JsonValueExpr *) expr)->expr,
+ collation);
+ 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;
@@ -1550,6 +1602,18 @@ exprLocation(const Node *expr)
case T_PartitionRangeDatum:
loc = ((const PartitionRangeDatum *) expr)->location;
break;
+ case T_JsonValueExpr:
+ loc = exprLocation((Node *) ((const JsonValueExpr *) expr)->expr);
+ break;
+ case T_JsonExpr:
+ {
+ const JsonExpr *jsexpr = (const JsonExpr *) expr;
+
+ /* consider both function name and leftmost arg */
+ loc = leftmostLoc(jsexpr->location,
+ exprLocation(jsexpr->raw_expr));
+ }
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2245,6 +2309,59 @@ expression_tree_walker(Node *node,
return true;
if (walker(tf->coldefexprs, context))
return true;
+ if (walker(tf->colvalexprs, context))
+ return true;
+ }
+ break;
+ case T_JsonValueExpr:
+ return walker(((JsonValueExpr *) node)->expr, context);
+ case T_JsonExpr:
+ {
+ JsonExpr *jexpr = (JsonExpr *) node;
+
+ if (walker(jexpr->raw_expr, context))
+ return true;
+ 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 (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:
@@ -3096,6 +3213,66 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->rowexpr, tf->rowexpr, Node *);
MUTATE(newnode->colexprs, tf->colexprs, List *);
MUTATE(newnode->coldefexprs, tf->coldefexprs, List *);
+ MUTATE(newnode->colvalexprs, tf->colvalexprs, List *);
+ return (Node *) newnode;
+ }
+ break;
+ case T_JsonValueExpr:
+ {
+ JsonValueExpr *jve = (JsonValueExpr *) node;
+ JsonValueExpr *newnode;
+
+ FLATCOPY(newnode, jve, JsonValueExpr);
+ MUTATE(newnode->expr, jve->expr, Expr *);
+
+ return (Node *) newnode;
+ }
+ break;
+ case T_JsonExpr:
+ {
+ JsonExpr *jexpr = (JsonExpr *) node;
+ JsonExpr *newnode;
+
+ FLATCOPY(newnode, jexpr, JsonExpr);
+ MUTATE(newnode->raw_expr, jexpr->path_spec, Node *);
+ MUTATE(newnode->raw_expr, jexpr->raw_expr, 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 */
+ 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;
@@ -3744,6 +3921,145 @@ raw_expression_tree_walker(Node *node,
break;
case T_CommonTableExpr:
return walker(((CommonTableExpr *) node)->ctequery, context);
+ case T_JsonValueExpr:
+ return walker(((JsonValueExpr *) node)->expr, context);
+ case T_JsonOutput:
+ return walker(((JsonOutput *) node)->typeName, context);
+ case T_JsonKeyValue:
+ {
+ JsonKeyValue *jkv = (JsonKeyValue *) node;
+
+ if (walker(jkv->key, context))
+ return true;
+ if (walker(jkv->value, context))
+ return true;
+ }
+ break;
+ case T_JsonObjectCtor:
+ {
+ JsonObjectCtor *joc = (JsonObjectCtor *) node;
+
+ if (walker(joc->output, context))
+ return true;
+ if (walker(joc->exprs, context))
+ return true;
+ }
+ break;
+ case T_JsonArrayCtor:
+ {
+ JsonArrayCtor *jac = (JsonArrayCtor *) node;
+
+ if (walker(jac->output, context))
+ return true;
+ if (walker(jac->exprs, context))
+ return true;
+ }
+ break;
+ case T_JsonObjectAgg:
+ {
+ JsonObjectAgg *joa = (JsonObjectAgg *) node;
+
+ if (walker(joa->ctor.output, context))
+ return true;
+ if (walker(joa->ctor.agg_order, context))
+ return true;
+ if (walker(joa->ctor.agg_filter, context))
+ return true;
+ if (walker(joa->ctor.over, context))
+ return true;
+ if (walker(joa->arg, context))
+ return true;
+ }
+ break;
+ case T_JsonArrayAgg:
+ {
+ JsonArrayAgg *jaa = (JsonArrayAgg *) node;
+
+ if (walker(jaa->ctor.output, context))
+ return true;
+ if (walker(jaa->ctor.agg_order, context))
+ return true;
+ if (walker(jaa->ctor.agg_filter, context))
+ return true;
+ if (walker(jaa->ctor.over, context))
+ return true;
+ if (walker(jaa->arg, context))
+ return true;
+ }
+ break;
+ case T_JsonArrayQueryCtor:
+ {
+ JsonArrayQueryCtor *jaqc = (JsonArrayQueryCtor *) node;
+
+ if (walker(jaqc->output, context))
+ return true;
+ if (walker(jaqc->query, context))
+ return true;
+ }
+ break;
+ case T_JsonIsPredicate:
+ return walker(((JsonIsPredicate *) node)->expr, context);
+ case T_JsonArgument:
+ return walker(((JsonArgument *) node)->val, context);
+ case T_JsonCommon:
+ {
+ JsonCommon *jc = (JsonCommon *) node;
+
+ if (walker(jc->expr, context))
+ return true;
+ if (walker(jc->pathspec, context))
+ return true;
+ if (walker(jc->passing, context))
+ return true;
+ }
+ break;
+ case T_JsonBehavior:
+ {
+ JsonBehavior *jb = (JsonBehavior *) node;
+
+ if (jb->btype == JSON_BEHAVIOR_DEFAULT &&
+ walker(jb->default_expr, context))
+ return true;
+ }
+ break;
+ case T_JsonFuncExpr:
+ {
+ JsonFuncExpr *jfe = (JsonFuncExpr *) node;
+
+ if (walker(jfe->common, context))
+ return true;
+ if (jfe->output && walker(jfe->output, context))
+ return true;
+ if (walker(jfe->on_empty, context))
+ return true;
+ if (walker(jfe->on_error, context))
+ return true;
+ }
+ break;
+ case T_JsonTable:
+ {
+ JsonTable *jt = (JsonTable *) node;
+
+ if (walker(jt->common, context))
+ return true;
+ if (walker(jt->columns, context))
+ return true;
+ }
+ break;
+ case T_JsonTableColumn:
+ {
+ JsonTableColumn *jtc = (JsonTableColumn *) node;
+
+ if (walker(jtc->typeName, context))
+ return true;
+ if (walker(jtc->on_empty, context))
+ return true;
+ if (walker(jtc->on_error, context))
+ return true;
+ if (jtc->coltype == JTC_NESTED && walker(jtc->columns, context))
+ return true;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8400dd319e..00b2944bae 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1026,6 +1026,7 @@ _outTableFunc(StringInfo str, const TableFunc *node)
{
WRITE_NODE_TYPE("TABLEFUNC");
+ WRITE_ENUM_FIELD(functype, TableFuncType);
WRITE_NODE_FIELD(ns_uris);
WRITE_NODE_FIELD(ns_names);
WRITE_NODE_FIELD(docexpr);
@@ -1036,7 +1037,9 @@ _outTableFunc(StringInfo str, const TableFunc *node)
WRITE_NODE_FIELD(colcollations);
WRITE_NODE_FIELD(colexprs);
WRITE_NODE_FIELD(coldefexprs);
+ WRITE_NODE_FIELD(colvalexprs);
WRITE_BITMAPSET_FIELD(notnulls);
+ WRITE_NODE_FIELD(plan);
WRITE_INT_FIELD(ordinalitycol);
WRITE_LOCATION_FIELD(location);
}
@@ -1126,6 +1129,8 @@ _outAggref(StringInfo str, const Aggref *node)
WRITE_CHAR_FIELD(aggkind);
WRITE_UINT_FIELD(agglevelsup);
WRITE_ENUM_FIELD(aggsplit, AggSplit);
+ WRITE_ENUM_FIELD(aggformat, FuncFormat);
+ WRITE_NODE_FIELD(aggformatopts);
WRITE_LOCATION_FIELD(location);
}
@@ -1155,6 +1160,8 @@ _outWindowFunc(StringInfo str, const WindowFunc *node)
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(winstar);
WRITE_BOOL_FIELD(winagg);
+ WRITE_ENUM_FIELD(winformat, FuncFormat);
+ WRITE_NODE_FIELD(winformatopts);
WRITE_LOCATION_FIELD(location);
}
@@ -1186,6 +1193,8 @@ _outFuncExpr(StringInfo str, const FuncExpr *node)
WRITE_OID_FIELD(funccollid);
WRITE_OID_FIELD(inputcollid);
WRITE_NODE_FIELD(args);
+ WRITE_ENUM_FIELD(funcformat2, FuncFormat);
+ WRITE_NODE_FIELD(funcformatopts);
WRITE_LOCATION_FIELD(location);
}
@@ -1680,6 +1689,121 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
WRITE_NODE_FIELD(exclRelTlist);
}
+static void
+_outJsonValueExpr(StringInfo str, const JsonValueExpr *node)
+{
+ WRITE_NODE_TYPE("JSONVALUEEXPR");
+
+ WRITE_NODE_FIELD(expr);
+ WRITE_ENUM_FIELD(format.type, JsonFormatType);
+ WRITE_ENUM_FIELD(format.encoding, JsonEncoding);
+ WRITE_LOCATION_FIELD(format.location);
+}
+
+static void
+_outJsonCtorOpts(StringInfo str, const JsonCtorOpts *node)
+{
+ WRITE_NODE_TYPE("JSONCTOROPTS");
+
+ WRITE_ENUM_FIELD(returning.format.type, JsonFormatType);
+ WRITE_ENUM_FIELD(returning.format.encoding, JsonEncoding);
+ WRITE_LOCATION_FIELD(returning.format.location);
+ WRITE_OID_FIELD(returning.typid);
+ WRITE_INT_FIELD(returning.typmod);
+ WRITE_BOOL_FIELD(unique);
+ WRITE_BOOL_FIELD(absent_on_null);
+}
+
+static void
+_outJsonExpr(StringInfo str, const JsonExpr *node)
+{
+ WRITE_NODE_TYPE("JSONEXPR");
+
+ WRITE_ENUM_FIELD(op, JsonExprOp);
+ WRITE_NODE_FIELD(raw_expr);
+ WRITE_NODE_FIELD(formatted_expr);
+ WRITE_NODE_FIELD(result_coercion);
+ WRITE_ENUM_FIELD(format.type, JsonFormatType);
+ WRITE_ENUM_FIELD(format.encoding, JsonEncoding);
+ WRITE_LOCATION_FIELD(format.location);
+ WRITE_NODE_FIELD(path_spec);
+ WRITE_NODE_FIELD(passing.values);
+ WRITE_NODE_FIELD(passing.names);
+ WRITE_ENUM_FIELD(returning.format.type, JsonFormatType);
+ WRITE_ENUM_FIELD(returning.format.encoding, JsonEncoding);
+ WRITE_LOCATION_FIELD(returning.format.location);
+ WRITE_OID_FIELD(returning.typid);
+ WRITE_INT_FIELD(returning.typmod);
+ WRITE_ENUM_FIELD(on_error.btype, JsonBehaviorType);
+ WRITE_NODE_FIELD(on_error.default_expr);
+ WRITE_ENUM_FIELD(on_empty.btype, JsonBehaviorType);
+ WRITE_NODE_FIELD(on_empty.default_expr);
+ 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);
+}
+
+static void
+_outJsonIsPredicateOpts(StringInfo str, const JsonIsPredicateOpts *node)
+{
+ WRITE_NODE_TYPE("JSONISOPTS");
+
+ WRITE_ENUM_FIELD(value_type, JsonValueType);
+ WRITE_BOOL_FIELD(unique_keys);
+}
+
+static void
+_outJsonTableParentNode(StringInfo str, const JsonTableParentNode *node)
+{
+ WRITE_NODE_TYPE("JSONTABPNODE");
+
+ WRITE_NODE_FIELD(path);
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(child);
+ WRITE_BOOL_FIELD(outerJoin);
+ WRITE_INT_FIELD(colMin);
+ WRITE_INT_FIELD(colMax);
+}
+
+static void
+_outJsonTableSiblingNode(StringInfo str, const JsonTableSiblingNode *node)
+{
+ WRITE_NODE_TYPE("JSONTABSNODE");
+
+ WRITE_NODE_FIELD(larg);
+ WRITE_NODE_FIELD(rarg);
+ WRITE_BOOL_FIELD(cross);
+}
+
/*****************************************************************************
*
* Stuff from pathnodes.h.
@@ -4274,6 +4398,30 @@ outNode(StringInfo str, const void *obj)
case T_PartitionRangeDatum:
_outPartitionRangeDatum(str, obj);
break;
+ case T_JsonValueExpr:
+ _outJsonValueExpr(str, obj);
+ break;
+ case T_JsonCtorOpts:
+ _outJsonCtorOpts(str, obj);
+ break;
+ case T_JsonIsPredicateOpts:
+ _outJsonIsPredicateOpts(str, obj);
+ break;
+ case T_JsonExpr:
+ _outJsonExpr(str, obj);
+ break;
+ case T_JsonCoercion:
+ _outJsonCoercion(str, obj);
+ break;
+ case T_JsonItemCoercions:
+ _outJsonItemCoercions(str, obj);
+ break;
+ case T_JsonTableParentNode:
+ _outJsonTableParentNode(str, obj);
+ break;
+ case T_JsonTableSiblingNode:
+ _outJsonTableSiblingNode(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6c2626ee62..ccab467b15 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,7 @@ _readTableFunc(void)
{
READ_LOCALS(TableFunc);
+ READ_ENUM_FIELD(functype, TableFuncType);
READ_NODE_FIELD(ns_uris);
READ_NODE_FIELD(ns_names);
READ_NODE_FIELD(docexpr);
@@ -502,7 +503,9 @@ _readTableFunc(void)
READ_NODE_FIELD(colcollations);
READ_NODE_FIELD(colexprs);
READ_NODE_FIELD(coldefexprs);
+ READ_NODE_FIELD(colvalexprs);
READ_BITMAPSET_FIELD(notnulls);
+ READ_NODE_FIELD(plan);
READ_INT_FIELD(ordinalitycol);
READ_LOCATION_FIELD(location);
@@ -614,6 +617,8 @@ _readAggref(void)
READ_CHAR_FIELD(aggkind);
READ_UINT_FIELD(agglevelsup);
READ_ENUM_FIELD(aggsplit, AggSplit);
+ READ_ENUM_FIELD(aggformat, FuncFormat);
+ READ_NODE_FIELD(aggformatopts);
READ_LOCATION_FIELD(location);
READ_DONE();
@@ -653,6 +658,8 @@ _readWindowFunc(void)
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(winstar);
READ_BOOL_FIELD(winagg);
+ READ_ENUM_FIELD(winformat, FuncFormat);
+ READ_NODE_FIELD(winformatopts);
READ_LOCATION_FIELD(location);
READ_DONE();
@@ -694,6 +701,8 @@ _readFuncExpr(void)
READ_OID_FIELD(funccollid);
READ_OID_FIELD(inputcollid);
READ_NODE_FIELD(args);
+ READ_ENUM_FIELD(funcformat2, FuncFormat);
+ READ_NODE_FIELD(funcformatopts);
READ_LOCATION_FIELD(location);
READ_DONE();
@@ -1342,6 +1351,154 @@ _readOnConflictExpr(void)
READ_DONE();
}
+/*
+ * _readJsonValueExpr
+ */
+static JsonValueExpr *
+_readJsonValueExpr(void)
+{
+ READ_LOCALS(JsonValueExpr);
+
+ READ_NODE_FIELD(expr);
+ READ_ENUM_FIELD(format.type, JsonFormatType);
+ READ_ENUM_FIELD(format.encoding, JsonEncoding);
+ READ_LOCATION_FIELD(format.location);
+
+ READ_DONE();
+}
+
+/*
+ * _readJsonCtorOpts
+ */
+static JsonCtorOpts *
+_readJsonCtorOpts(void)
+{
+ READ_LOCALS(JsonCtorOpts);
+ READ_ENUM_FIELD(returning.format.type, JsonFormatType);
+ READ_ENUM_FIELD(returning.format.encoding, JsonEncoding);
+ READ_LOCATION_FIELD(returning.format.location);
+ READ_OID_FIELD(returning.typid);
+ READ_INT_FIELD(returning.typmod);
+ READ_BOOL_FIELD(unique);
+ READ_BOOL_FIELD(absent_on_null);
+
+ READ_DONE();
+}
+
+/*
+ * _readJsonExpr
+ */
+static JsonExpr *
+_readJsonExpr(void)
+{
+ READ_LOCALS(JsonExpr);
+
+ READ_ENUM_FIELD(op, JsonExprOp);
+ READ_NODE_FIELD(raw_expr);
+ READ_NODE_FIELD(formatted_expr);
+ READ_NODE_FIELD(result_coercion);
+ READ_ENUM_FIELD(format.type, JsonFormatType);
+ READ_ENUM_FIELD(format.encoding, JsonEncoding);
+ READ_LOCATION_FIELD(format.location);
+ READ_NODE_FIELD(path_spec);
+ READ_NODE_FIELD(passing.values);
+ READ_NODE_FIELD(passing.names);
+ READ_ENUM_FIELD(returning.format.type, JsonFormatType);
+ READ_ENUM_FIELD(returning.format.encoding, JsonEncoding);
+ READ_LOCATION_FIELD(returning.format.location);
+ READ_OID_FIELD(returning.typid);
+ READ_INT_FIELD(returning.typmod);
+ READ_ENUM_FIELD(on_error.btype, JsonBehaviorType);
+ READ_NODE_FIELD(on_error.default_expr);
+ READ_ENUM_FIELD(on_empty.btype, JsonBehaviorType);
+ READ_NODE_FIELD(on_empty.default_expr);
+ READ_NODE_FIELD(coercions);
+ READ_ENUM_FIELD(wrapper, JsonWrapper);
+ READ_BOOL_FIELD(omit_quotes);
+ READ_LOCATION_FIELD(location);
+
+ READ_DONE();
+}
+
+static JsonTableParentNode *
+_readJsonTableParentNode(void)
+{
+ READ_LOCALS(JsonTableParentNode);
+
+ READ_NODE_FIELD(path);
+ READ_STRING_FIELD(name);
+ READ_NODE_FIELD(child);
+ READ_BOOL_FIELD(outerJoin);
+ READ_INT_FIELD(colMin);
+ READ_INT_FIELD(colMax);
+
+ READ_DONE();
+}
+
+static JsonTableSiblingNode *
+_readJsonTableSiblingNode(void)
+{
+ READ_LOCALS(JsonTableSiblingNode);
+
+ READ_NODE_FIELD(larg);
+ READ_NODE_FIELD(rarg);
+ READ_BOOL_FIELD(cross);
+
+ 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();
+}
+
+/*
+ * _readJsonIsPredicateOpts
+ */
+static JsonIsPredicateOpts *
+_readJsonIsPredicateOpts()
+{
+ READ_LOCALS(JsonIsPredicateOpts);
+
+ READ_ENUM_FIELD(value_type, JsonValueType);
+ READ_BOOL_FIELD(unique_keys);
+
+ READ_DONE();
+}
+
/*
* Stuff from parsenodes.h.
*/
@@ -2805,6 +2962,22 @@ parseNodeString(void)
return_value = _readPartitionBoundSpec();
else if (MATCH("PARTITIONRANGEDATUM", 19))
return_value = _readPartitionRangeDatum();
+ else if (MATCH("JSONVALUEEXPR", 13))
+ return_value = _readJsonValueExpr();
+ else if (MATCH("JSONCTOROPTS", 12))
+ return_value = _readJsonCtorOpts();
+ else if (MATCH("JSONISOPTS", 10))
+ return_value = _readJsonIsPredicateOpts();
+ 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 if (MATCH("JSONTABPNODE", 12))
+ return_value = _readJsonTableParentNode();
+ else if (MATCH("JSONTABSNODE", 12))
+ return_value = _readJsonTableSiblingNode();
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 a2a9b1f7be..7da18f5cc8 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -4022,7 +4022,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 2e84d6b3b4..6d26f1740c 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"
@@ -1068,6 +1069,16 @@ 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,
@@ -2453,6 +2464,8 @@ eval_const_expressions_mutator(Node *node,
newexpr->winref = expr->winref;
newexpr->winstar = expr->winstar;
newexpr->winagg = expr->winagg;
+ newexpr->winformat = expr->winformat;
+ newexpr->winformatopts = copyObject(expr->winformatopts);
newexpr->location = expr->location;
return (Node *) newexpr;
@@ -2499,6 +2512,8 @@ eval_const_expressions_mutator(Node *node,
newexpr->funccollid = expr->funccollid;
newexpr->inputcollid = expr->inputcollid;
newexpr->args = args;
+ newexpr->funcformat2 = expr->funcformat2;
+ newexpr->funcformatopts = copyObject(expr->funcformatopts);
newexpr->location = expr->location;
return (Node *) newexpr;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8311b1dd46..d6921fbe9c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -212,6 +212,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
JoinType jtype;
DropBehavior dbehavior;
OnCommitAction oncommit;
+ JsonFormat jsformat;
List *list;
Node *node;
Value *value;
@@ -242,6 +243,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ JsonBehavior *jsbehavior;
+ struct {
+ JsonBehavior *on_empty;
+ JsonBehavior *on_error;
+ } on_behavior;
+ JsonQuotes js_quotes;
}
%type stmt schema_stmt
@@ -590,6 +597,99 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type hash_partbound
%type hash_partbound_elem
+%type 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_value_constructor
+ json_object_constructor
+ json_object_constructor_args_opt
+ json_object_args
+ json_object_ctor_args_opt
+ json_object_func_args
+ json_array_constructor
+ json_name_and_value
+ json_aggregate_func
+ json_object_aggregate_constructor
+ json_array_aggregate_constructor
+ json_path_specification
+ json_table
+ json_table_column_definition
+ json_table_ordinality_column_definition
+ json_table_regular_column_definition
+ json_table_formatted_column_definition
+ json_table_nested_columns
+ json_table_plan_clause_opt
+ json_table_specific_plan
+ json_table_plan
+ json_table_plan_simple
+ json_table_plan_parent_child
+ json_table_plan_outer
+ json_table_plan_inner
+ json_table_plan_sibling
+ json_table_plan_union
+ json_table_plan_cross
+ json_table_plan_primary
+ json_table_default_plan
+
+%type json_arguments
+ json_passing_clause_opt
+ json_table_columns_clause
+ json_table_column_definition_list
+ json_name_and_value_list
+ json_value_expr_list
+ json_array_aggregate_order_by_clause_opt
+
+%type json_returning_clause_opt
+
+%type json_table_path_name
+ json_as_path_name_clause_opt
+ json_table_column_path_specification_clause_opt
+
+%type json_encoding
+ json_encoding_clause_opt
+ json_table_default_plan_choices
+ json_table_default_plan_inner_outer
+ json_table_default_plan_union_cross
+ json_wrapper_clause_opt
+ json_wrapper_behavior
+ json_conditional_or_unconditional_opt
+ json_predicate_type_constraint_opt
+
+%type json_format_clause_opt
+ json_representation
+
+%type json_behavior_error
+ json_behavior_null
+ json_behavior_true
+ json_behavior_false
+ json_behavior_unknown
+ json_behavior_empty
+ 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
+ json_table_error_behavior
+ json_table_error_clause_opt
+
+%type json_value_on_behavior_clause_opt
+ json_query_on_behavior_clause_opt
+
+%type json_quotes_behavior
+ json_quotes_clause_opt
+
+%type 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
@@ -612,7 +712,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
*/
/* ordinary key words in alphabetical order */
-%token ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
+%token ABORT_P ABSENT ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
@@ -622,8 +722,8 @@ 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 CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
- CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+ COMMITTED 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
CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -633,12 +733,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
- EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
- EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
+ EACH ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE
+ EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FILTER 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
@@ -649,9 +749,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
- JOIN
+ JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG
+ JSON_QUERY JSON_TABLE JSON_VALUE JSONB
- KEY
+ KEY KEYS KEEP
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
@@ -659,29 +760,29 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
- NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE
+ NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NO NONE
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
- PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+ PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLAN PLANS POLICY
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
- QUOTE
+ QUOTE QUOTES
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
- SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
- SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
- SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
- START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
+ SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
+ SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF
+ SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
+ START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING STRIP_P
SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
@@ -689,8 +790,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TREAT TRIGGER TRIM TRUE_P
TRUNCATE TRUSTED TYPE_P TYPES_P
- UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED
- UNTIL UPDATE USER USING
+ UNBOUNDED UNCOMMITTED UNCONDITIONAL UNENCRYPTED UNION UNIQUE UNKNOWN
+ UNLISTEN UNLOGGED UNTIL UPDATE USER USING
VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
VERBOSE VERSION_P VIEW VIEWS VOLATILE
@@ -714,11 +815,11 @@ 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
/* Precedence: lowest to highest */
%nonassoc SET /* see relation_expr_opt_alias */
+%right FORMAT
%left UNION EXCEPT
%left INTERSECT
%left OR
@@ -757,6 +858,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* blame any funny behavior of UNBOUNDED on the SQL standard, though.
*/
%nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */
+%nonassoc ERROR_P EMPTY_P DEFAULT ABSENT /* JSON error/empty behavior */
+%nonassoc COLUMNS FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN
%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
%left Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-'
@@ -781,6 +884,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* kluge to keep xml_whitespace_option from causing shift/reduce conflicts */
%right PRESERVE STRIP_P
+%nonassoc json_table_column
+%nonassoc NESTED
+%left PATH
+
+%nonassoc empty_json_unique
+%left WITHOUT WITH_LA_UNIQUE
+
%%
/*
@@ -11930,6 +12040,19 @@ table_ref: relation_expr opt_alias_clause
$2->alias = $4;
$$ = (Node *) $2;
}
+ | json_table opt_alias_clause
+ {
+ JsonTable *jt = castNode(JsonTable, $1);
+ jt->alias = $2;
+ $$ = (Node *) jt;
+ }
+ | LATERAL_P json_table opt_alias_clause
+ {
+ JsonTable *jt = castNode(JsonTable, $2);
+ jt->alias = $3;
+ jt->lateral = true;
+ $$ = (Node *) jt;
+ }
;
@@ -12432,6 +12555,8 @@ xmltable_column_option_el:
{ $$ = makeDefElem("is_not_null", (Node *) makeInteger(true), @1); }
| NULL_P
{ $$ = makeDefElem("is_not_null", (Node *) makeInteger(false), @1); }
+ | PATH b_expr
+ { $$ = makeDefElem("path", $2, @1); }
;
xml_namespace_list:
@@ -12843,7 +12968,7 @@ ConstInterval:
opt_timezone:
WITH_LA TIME ZONE { $$ = true; }
- | WITHOUT TIME ZONE { $$ = false; }
+ | WITHOUT_LA TIME ZONE { $$ = false; }
| /*EMPTY*/ { $$ = false; }
;
@@ -13344,6 +13469,40 @@ a_expr: c_expr { $$ = $1; }
list_make1($1), @2),
@2);
}
+ | a_expr
+ IS JSON
+ json_predicate_type_constraint_opt
+ json_key_uniqueness_constraint_opt %prec IS
+ {
+ JsonFormat format = { JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1 };
+ $$ = makeJsonIsPredicate($1, format, $4, $5);
+ }
+ | a_expr
+ FORMAT json_representation
+ IS JSON
+ json_predicate_type_constraint_opt
+ json_key_uniqueness_constraint_opt %prec FORMAT
+ {
+ $3.location = @2;
+ $$ = makeJsonIsPredicate($1, $3, $6, $7);
+ }
+ | a_expr
+ IS NOT JSON
+ json_predicate_type_constraint_opt
+ json_key_uniqueness_constraint_opt %prec IS
+ {
+ JsonFormat format = { JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1 };
+ $$ = makeNotExpr(makeJsonIsPredicate($1, format, $5, $6), @1);
+ }
+ | a_expr
+ FORMAT json_representation
+ IS NOT JSON
+ json_predicate_type_constraint_opt
+ json_key_uniqueness_constraint_opt %prec FORMAT
+ {
+ $3.location = @2;
+ $$ = makeNotExpr(makeJsonIsPredicate($1, $3, $7, $8), @1);
+ }
| DEFAULT
{
/*
@@ -13436,6 +13595,25 @@ b_expr: c_expr
}
;
+json_predicate_type_constraint_opt:
+ VALUE_P { $$ = JS_TYPE_ANY; }
+ | ARRAY { $$ = JS_TYPE_ARRAY; }
+ | OBJECT_P { $$ = JS_TYPE_OBJECT; }
+ | SCALAR { $$ = JS_TYPE_SCALAR; }
+ | /* EMPTY */ { $$ = JS_TYPE_ANY; }
+ ;
+
+json_key_uniqueness_constraint_opt:
+ WITH_LA_UNIQUE UNIQUE opt_keys { $$ = true; }
+ | WITHOUT UNIQUE opt_keys { $$ = false; }
+ | /* EMPTY */ %prec empty_json_unique { $$ = false; }
+ ;
+
+opt_keys:
+ KEYS { }
+ | /* EMPTY */ { }
+ ;
+
/*
* Productions that can be used in both a_expr and b_expr.
*
@@ -13696,6 +13874,13 @@ func_expr: func_application within_group_clause filter_clause over_clause
n->over = $4;
$$ = (Node *) n;
}
+ | json_aggregate_func filter_clause over_clause
+ {
+ JsonAggCtor *n = (JsonAggCtor *) $1;
+ n->agg_filter = $2;
+ n->over = $3;
+ $$ = (Node *) $1;
+ }
| func_expr_common_subexpr
{ $$ = $1; }
;
@@ -13709,6 +13894,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; }
;
/*
@@ -13930,6 +14116,8 @@ func_expr_common_subexpr:
n->location = @1;
$$ = (Node *)n;
}
+ | json_func_expr
+ { $$ = $1; }
;
/*
@@ -14623,6 +14811,734 @@ opt_asymmetric: ASYMMETRIC
| /*EMPTY*/
;
+/* SQL/JSON support */
+json_func_expr:
+ json_value_func_expr
+ | json_query_expr
+ | json_exists_predicate
+ | json_value_constructor
+ ;
+
+
+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;
+ if ($4)
+ {
+ n->output = (JsonOutput *) makeNode(JsonOutput);
+ n->output->typeName = $4;
+ n->output->returning.format.location = @4;
+ n->output->returning.format.type = JS_FORMAT_DEFAULT;
+ n->output->returning.format.encoding = JS_ENC_DEFAULT;
+ }
+ else
+ n->output = NULL;
+ n->on_empty = $5.on_empty;
+ n->on_error = $5.on_error;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_api_common_syntax:
+ json_context_item ',' json_path_specification
+ json_as_path_name_clause_opt
+ json_passing_clause_opt
+ {
+ JsonCommon *n = makeNode(JsonCommon);
+ n->expr = (JsonValueExpr *) $1;
+ n->pathspec = $3;
+ n->pathname = $4;
+ n->passing = $5;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_context_item:
+ json_value_expr { $$ = $1; }
+ ;
+
+json_path_specification:
+ a_expr { $$ = $1; }
+ ;
+
+json_as_path_name_clause_opt:
+ AS json_table_path_name { $$ = $2; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+json_table_path_name:
+ name { $$ = $1; }
+ ;
+
+json_passing_clause_opt:
+ PASSING json_arguments { $$ = $2; }
+ | /* EMPTY */ { $$ = NIL; }
+ ;
+
+json_arguments:
+ json_argument { $$ = list_make1($1); }
+ | json_arguments ',' json_argument { $$ = lappend($1, $3); }
+ ;
+
+json_argument:
+ json_value_expr AS ColLabel
+ {
+ JsonArgument *n = makeNode(JsonArgument);
+ n->val = (JsonValueExpr *) $1;
+ n->name = $3;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_value_expr:
+ a_expr json_format_clause_opt
+ {
+ $$ = (Node *) makeJsonValueExpr((Expr *) $1, $2);
+ }
+ ;
+
+json_format_clause_opt:
+ FORMAT json_representation
+ {
+ $$ = $2;
+ $$.location = @1;
+ }
+ | /* EMPTY */
+ {
+ $$.type = JS_FORMAT_DEFAULT;
+ $$.encoding = JS_ENC_DEFAULT;
+ $$.location = -1;
+ }
+ ;
+
+json_representation:
+ JSON json_encoding_clause_opt
+ {
+ $$.type = JS_FORMAT_JSON;
+ $$.encoding = $2;
+ $$.location = @1;
+ }
+ | JSONB
+ {
+ $$.type = JS_FORMAT_JSONB;
+ $$.encoding = JS_ENC_DEFAULT;
+ $$.location = @1;
+ }
+ /* | implementation_defined_JSON_representation_option (BSON, AVRO etc) */
+ ;
+
+json_encoding_clause_opt:
+ ENCODING json_encoding { $$ = $2; }
+ | /* EMPTY */ { $$ = JS_ENC_DEFAULT; }
+ ;
+
+json_encoding:
+ name { $$ = makeJsonEncoding($1); }
+ /*
+ | UTF8 { $$ = JS_ENC_UTF8; }
+ | UTF16 { $$ = JS_ENC_UTF16; }
+ | UTF32 { $$ = JS_ENC_UTF32; }
+ */
+ ;
+
+json_returning_clause_opt:
+ RETURNING Typename { $$ = $2; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+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:
+ EMPTY_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL); }
+ ;
+
+json_behavior_empty_array:
+ EMPTY_P ARRAY { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); }
+ ;
+
+json_behavior_empty_object:
+ EMPTY_P OBJECT_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL); }
+ ;
+
+json_behavior_default:
+ DEFAULT a_expr { $$ = makeJsonBehavior(JSON_BEHAVIOR_DEFAULT, $2); }
+ ;
+
+
+json_value_behavior:
+ json_behavior_null
+ | json_behavior_error
+ | json_behavior_default
+ ;
+
+json_value_on_behavior_clause_opt:
+ json_value_behavior ON EMPTY_P
+ { $$.on_empty = $1; $$.on_error = NULL; }
+ | json_value_behavior ON EMPTY_P json_value_behavior ON ERROR_P
+ { $$.on_empty = $1; $$.on_error = $4; }
+ | json_value_behavior ON ERROR_P
+ { $$.on_empty = NULL; $$.on_error = $1; }
+ | /* EMPTY */
+ { $$.on_empty = NULL; $$.on_error = NULL; }
+ ;
+
+json_query_expr:
+ JSON_QUERY '('
+ json_api_common_syntax
+ json_output_clause_opt
+ json_wrapper_clause_opt
+ json_quotes_clause_opt
+ json_query_on_behavior_clause_opt
+ ')'
+ {
+ JsonFuncExpr *n = makeNode(JsonFuncExpr);
+ n->op = IS_JSON_QUERY;
+ n->common = (JsonCommon *) $3;
+ n->output = (JsonOutput *) $4;
+ n->wrapper = $5;
+ if (n->wrapper != JSW_NONE && $6 != JS_QUOTES_UNSPEC)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used"),
+ parser_errposition(@6)));
+ n->omit_quotes = $6 == JS_QUOTES_OMIT;
+ n->on_empty = $7.on_empty;
+ n->on_error = $7.on_error;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_wrapper_clause_opt:
+ json_wrapper_behavior WRAPPER { $$ = $1; }
+ | /* EMPTY */ { $$ = 0; }
+ ;
+
+json_wrapper_behavior:
+ WITHOUT array_opt { $$ = JSW_NONE; }
+ | WITH json_conditional_or_unconditional_opt array_opt { $$ = $2; }
+ ;
+
+array_opt:
+ ARRAY { }
+ | /* EMPTY */ { }
+ ;
+
+json_conditional_or_unconditional_opt:
+ CONDITIONAL { $$ = JSW_CONDITIONAL; }
+ | UNCONDITIONAL { $$ = JSW_UNCONDITIONAL; }
+ | /* EMPTY */ { $$ = JSW_UNCONDITIONAL; }
+ ;
+
+json_quotes_clause_opt:
+ json_quotes_behavior QUOTES json_on_scalar_string_opt { $$ = $1; }
+ | /* EMPTY */ { $$ = JS_QUOTES_UNSPEC; }
+ ;
+
+json_quotes_behavior:
+ KEEP { $$ = JS_QUOTES_KEEP; }
+ | OMIT { $$ = JS_QUOTES_OMIT; }
+ ;
+
+json_on_scalar_string_opt:
+ ON SCALAR STRING { }
+ | /* EMPTY */ { }
+ ;
+
+json_query_behavior:
+ json_behavior_error
+ | json_behavior_null
+ | json_behavior_empty_array
+ | json_behavior_empty_object
+ ;
+
+json_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_table:
+ JSON_TABLE '('
+ json_api_common_syntax
+ json_table_columns_clause
+ json_table_plan_clause_opt
+ json_table_error_clause_opt
+ ')'
+ {
+ JsonTable *n = makeNode(JsonTable);
+ n->common = (JsonCommon *) $3;
+ n->columns = $4;
+ n->plan = (JsonTablePlan *) $5;
+ n->on_error = $6;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_table_columns_clause:
+ COLUMNS '(' json_table_column_definition_list ')' { $$ = $3; }
+ ;
+
+json_table_column_definition_list:
+ json_table_column_definition
+ { $$ = list_make1($1); }
+ | json_table_column_definition_list ',' json_table_column_definition
+ { $$ = lappend($1, $3); }
+ ;
+
+json_table_column_definition:
+ json_table_ordinality_column_definition %prec json_table_column
+ | json_table_regular_column_definition %prec json_table_column
+ | json_table_formatted_column_definition %prec json_table_column
+ | json_table_nested_columns
+ ;
+
+json_table_ordinality_column_definition:
+ ColId FOR ORDINALITY
+ {
+ JsonTableColumn *n = makeNode(JsonTableColumn);
+ n->coltype = JTC_FOR_ORDINALITY;
+ n->name = $1;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_table_regular_column_definition:
+ ColId Typename
+ json_table_column_path_specification_clause_opt
+ json_value_on_behavior_clause_opt
+ {
+ JsonTableColumn *n = makeNode(JsonTableColumn);
+ n->coltype = JTC_REGULAR;
+ n->name = $1;
+ n->typeName = $2;
+ n->format.type = JS_FORMAT_DEFAULT;
+ n->format.encoding = JS_ENC_DEFAULT;
+ n->wrapper = JSW_NONE;
+ n->omit_quotes = false;
+ n->pathspec = $3;
+ n->on_empty = $4.on_empty;
+ n->on_error = $4.on_error;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_table_error_behavior:
+ json_behavior_error
+ | json_behavior_empty
+ ;
+
+json_table_error_clause_opt:
+ json_table_error_behavior ON ERROR_P { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+json_table_column_path_specification_clause_opt:
+ PATH Sconst { $$ = $2; }
+ | /* EMPTY */ %prec json_table_column { $$ = NULL; }
+ ;
+
+json_table_formatted_column_definition:
+ ColId Typename FORMAT json_representation
+ json_table_column_path_specification_clause_opt
+ json_wrapper_clause_opt
+ json_quotes_clause_opt
+ json_query_on_behavior_clause_opt
+ {
+ JsonTableColumn *n = makeNode(JsonTableColumn);
+ n->coltype = JTC_FORMATTED;
+ n->name = $1;
+ n->typeName = $2;
+ n->format = $4;
+ n->pathspec = $5;
+ n->wrapper = $6;
+ if (n->wrapper != JSW_NONE && $7 != JS_QUOTES_UNSPEC)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used"),
+ parser_errposition(@7)));
+ n->omit_quotes = $7 == JS_QUOTES_OMIT;
+ n->on_empty = $8.on_empty;
+ n->on_error = $8.on_error;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_table_nested_columns:
+ NESTED path_opt Sconst
+ json_as_path_name_clause_opt
+ json_table_columns_clause
+ {
+ JsonTableColumn *n = makeNode(JsonTableColumn);
+ n->coltype = JTC_NESTED;
+ n->pathspec = $3;
+ n->pathname = $4;
+ n->columns = $5;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+path_opt:
+ PATH { }
+ | /* EMPTY */ { }
+ ;
+
+json_table_plan_clause_opt:
+ json_table_specific_plan { $$ = $1; }
+ | json_table_default_plan { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+json_table_specific_plan:
+ PLAN '(' json_table_plan ')' { $$ = $3; }
+ ;
+
+json_table_plan:
+ json_table_plan_simple
+ | json_table_plan_parent_child
+ | json_table_plan_sibling
+ ;
+
+json_table_plan_simple:
+ json_table_path_name
+ {
+ JsonTablePlan *n = makeNode(JsonTablePlan);
+ n->plan_type = JSTP_SIMPLE;
+ n->pathname = $1;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_table_plan_parent_child:
+ json_table_plan_outer
+ | json_table_plan_inner
+ ;
+
+json_table_plan_outer:
+ json_table_plan_simple OUTER_P json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_OUTER, $1, $3, @1); }
+ ;
+
+json_table_plan_inner:
+ json_table_plan_simple INNER_P json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_INNER, $1, $3, @1); }
+ ;
+
+json_table_plan_sibling:
+ json_table_plan_union
+ | json_table_plan_cross
+ ;
+
+json_table_plan_union:
+ json_table_plan_primary UNION json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_UNION, $1, $3, @1); }
+ | json_table_plan_union UNION json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_UNION, $1, $3, @1); }
+ ;
+
+json_table_plan_cross:
+ json_table_plan_primary CROSS json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_CROSS, $1, $3, @1); }
+ | json_table_plan_cross CROSS json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_CROSS, $1, $3, @1); }
+ ;
+
+json_table_plan_primary:
+ json_table_plan_simple { $$ = $1; }
+ | '(' json_table_plan ')'
+ {
+ castNode(JsonTablePlan, $2)->location = @1;
+ $$ = $2;
+ }
+ ;
+
+json_table_default_plan:
+ PLAN DEFAULT '(' json_table_default_plan_choices ')'
+ {
+ JsonTablePlan *n = makeNode(JsonTablePlan);
+ n->plan_type = JSTP_DEFAULT;
+ n->join_type = $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_table_default_plan_choices:
+ json_table_default_plan_inner_outer { $$ = $1 | JSTP_UNION; }
+ | json_table_default_plan_inner_outer ','
+ json_table_default_plan_union_cross { $$ = $1 | $3; }
+ | json_table_default_plan_union_cross { $$ = $1 | JSTP_OUTER; }
+ | json_table_default_plan_union_cross ','
+ json_table_default_plan_inner_outer { $$ = $1 | $3; }
+ ;
+
+json_table_default_plan_inner_outer:
+ INNER_P { $$ = JSTP_INNER; }
+ | OUTER_P { $$ = JSTP_OUTER; }
+ ;
+
+json_table_default_plan_union_cross:
+ UNION { $$ = JSTP_UNION; }
+ | CROSS { $$ = JSTP_CROSS; }
+ ;
+
+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; }
+ ;
+
+json_exists_predicate:
+ JSON_EXISTS '('
+ json_api_common_syntax
+ json_exists_error_clause_opt
+ ')'
+ {
+ JsonFuncExpr *p = makeNode(JsonFuncExpr);
+ p->op = IS_JSON_EXISTS;
+ p->common = (JsonCommon *) $3;
+ p->on_error = $4;
+ p->location = @1;
+ $$ = (Node *) p;
+ }
+ ;
+
+json_exists_error_clause_opt:
+ json_exists_error_behavior ON ERROR_P { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+json_exists_error_behavior:
+ json_behavior_error
+ | json_behavior_true
+ | json_behavior_false
+ | json_behavior_unknown
+ ;
+
+json_value_constructor:
+ json_object_constructor
+ | json_array_constructor
+ ;
+
+json_object_constructor:
+ JSON_OBJECT '(' json_object_args ')'
+ {
+ $$ = $3;
+ }
+ ;
+
+json_object_args:
+ json_object_ctor_args_opt
+ | json_object_func_args
+ ;
+
+json_object_func_args:
+ func_arg_list
+ {
+ List *func = list_make1(makeString("json_object"));
+ $$ = (Node *) makeFuncCall(func, $1, @1);
+ }
+ ;
+
+json_object_ctor_args_opt:
+ json_object_constructor_args_opt json_output_clause_opt
+ {
+ JsonObjectCtor *n = (JsonObjectCtor *) $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
+ {
+ JsonObjectCtor *n = makeNode(JsonObjectCtor);
+ n->exprs = $1;
+ n->absent_on_null = $2;
+ n->unique = $3;
+ $$ = (Node *) n;
+ }
+ | /* EMPTY */
+ {
+ JsonObjectCtor *n = makeNode(JsonObjectCtor);
+ 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
+ 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
+ ')'
+ {
+ JsonArrayCtor *n = makeNode(JsonArrayCtor);
+ 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
+ ')'
+ {
+ JsonArrayQueryCtor *n = makeNode(JsonArrayQueryCtor);
+ n->query = $3;
+ /* n->format = $4; */
+ n->absent_on_null = true /* $5 */;
+ n->output = (JsonOutput *) $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ | JSON_ARRAY '('
+ json_output_clause_opt
+ ')'
+ {
+ JsonArrayCtor *n = makeNode(JsonArrayCtor);
+ 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->ctor.output = (JsonOutput *) $6;
+ n->ctor.agg_order = NULL;
+ n->ctor.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->ctor.agg_order = $4;
+ n->absent_on_null = $5;
+ n->ctor.output = (JsonOutput *) $6;
+ n->ctor.location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_array_aggregate_order_by_clause_opt:
+ ORDER BY sortby_list { $$ = $3; }
+ | /* EMPTY */ { $$ = NIL; }
+ ;
/*****************************************************************************
*
@@ -15019,6 +15935,7 @@ ColLabel: IDENT { $$ = $1; }
*/
unreserved_keyword:
ABORT_P
+ | ABSENT
| ABSOLUTE_P
| ACCESS
| ACTION
@@ -15055,6 +15972,7 @@ unreserved_keyword:
| COMMENTS
| COMMIT
| COMMITTED
+ | CONDITIONAL
| CONFIGURATION
| CONFLICT
| CONNECTION
@@ -15090,10 +16008,12 @@ unreserved_keyword:
| DOUBLE_P
| DROP
| EACH
+ | EMPTY_P
| ENABLE_P
| ENCODING
| ENCRYPTED
| ENUM_P
+ | ERROR_P
| ESCAPE
| EVENT
| EXCLUDE
@@ -15139,7 +16059,11 @@ unreserved_keyword:
| INSTEAD
| INVOKER
| ISOLATION
+ | JSON
+ | JSONB
+ | KEEP
| KEY
+ | KEYS
| LABEL
| LANGUAGE
| LARGE_P
@@ -15165,6 +16089,7 @@ unreserved_keyword:
| MOVE
| NAME_P
| NAMES
+ | NESTED
| NEW
| NEXT
| NO
@@ -15177,6 +16102,7 @@ unreserved_keyword:
| OFF
| OIDS
| OLD
+ | OMIT
| OPERATOR
| OPTION
| OPTIONS
@@ -15192,6 +16118,8 @@ unreserved_keyword:
| PARTITION
| PASSING
| PASSWORD
+ | PATH
+ | PLAN
| PLANS
| POLICY
| PRECEDING
@@ -15206,6 +16134,7 @@ unreserved_keyword:
| PROGRAM
| PUBLICATION
| QUOTE
+ | QUOTES
| RANGE
| READ
| REASSIGN
@@ -15234,6 +16163,7 @@ unreserved_keyword:
| ROWS
| RULE
| SAVEPOINT
+ | SCALAR
| SCHEMA
| SCHEMAS
| SCROLL
@@ -15284,6 +16214,7 @@ unreserved_keyword:
| TYPES_P
| UNBOUNDED
| UNCOMMITTED
+ | UNCONDITIONAL
| UNENCRYPTED
| UNKNOWN
| UNLISTEN
@@ -15341,6 +16272,14 @@ col_name_keyword:
| INT_P
| INTEGER
| INTERVAL
+ | JSON_ARRAY
+ | JSON_ARRAYAGG
+ | JSON_EXISTS
+ | JSON_OBJECT
+ | JSON_OBJECTAGG
+ | JSON_QUERY
+ | JSON_TABLE
+ | JSON_VALUE
| LEAST
| NATIONAL
| NCHAR
@@ -15392,6 +16331,7 @@ type_func_name_keyword:
| CONCURRENTLY
| CROSS
| CURRENT_SCHEMA
+ | FORMAT
| FREEZE
| FULL
| ILIKE
@@ -15407,6 +16347,7 @@ type_func_name_keyword:
| OVERLAPS
| RIGHT
| SIMILAR
+ | STRING
| TABLESAMPLE
| VERBOSE
;
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 2a6b2ff153..c2671f0f6d 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -48,10 +48,20 @@
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/catcache.h"
+#include "utils/json.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/rel.h"
+/* Context for JSON_TABLE transformation */
+typedef struct JsonTableContext
+{
+ JsonTable *table; /* untransformed node */
+ TableFunc *tablefunc; /* transformed node */
+ List *pathNames; /* list of all path and columns names */
+ int pathNameId; /* path name id counter */
+ Oid contextItemTypid; /* type oid of context item (json/jsonb) */
+} JsonTableContext;
/* Convenience macro for the most common makeNamespaceItem() case */
#define makeDefaultNSItem(rte) makeNamespaceItem(rte, true, true, false, true)
@@ -102,6 +112,13 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
Node *clause);
+static JsonTableParentNode * transformJsonTableColumns(ParseState *pstate,
+ JsonTableContext *cxt,
+ JsonTablePlan *plan,
+ List *columns,
+ char *pathSpec,
+ char **pathName,
+ int location);
/*
@@ -720,6 +737,8 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf)
Assert(!pstate->p_lateral_active);
pstate->p_lateral_active = true;
+ tf->functype = TFT_XMLTABLE;
+
/* Transform and apply typecast to the row-generating expression ... */
Assert(rtf->rowexpr != NULL);
tf->rowexpr = coerce_to_specific_type(pstate,
@@ -1010,6 +1029,18 @@ transformRangeTableSample(ParseState *pstate, RangeTableSample *rts)
return tablesample;
}
+static Node *
+makeStringConst(char *str, int location)
+{
+ A_Const *n = makeNode(A_Const);
+
+ n->val.type = T_String;
+ n->val.val.str = str;
+ n->location = location;
+
+ return (Node *)n;
+}
+
/*
* getRTEForSpecialRelationTypes
*
@@ -1040,6 +1071,642 @@ getRTEForSpecialRelationTypes(ParseState *pstate, RangeVar *rv)
return rte;
}
+/*
+ * Transform JSON_TABLE column
+ * - regular column into JSON_VALUE()
+ * - formatted column into JSON_QUERY()
+ */
+static Node *
+transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
+ List *passingArgs, bool errorOnError)
+{
+ JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
+ JsonValueExpr *jvexpr = makeNode(JsonValueExpr);
+ JsonCommon *common = makeNode(JsonCommon);
+ JsonOutput *output = makeNode(JsonOutput);
+ JsonPathSpec pathspec;
+
+ jfexpr->op = jtc->coltype == JTC_REGULAR ? IS_JSON_VALUE : IS_JSON_QUERY;
+ jfexpr->common = common;
+ jfexpr->output = output;
+ jfexpr->on_empty = jtc->on_empty;
+ jfexpr->on_error = jtc->on_error;
+ if (!jfexpr->on_error && errorOnError)
+ jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL);
+ jfexpr->omit_quotes = jtc->omit_quotes;
+ jfexpr->wrapper = jtc->wrapper;
+ jfexpr->location = jtc->location;
+
+ output->typeName = jtc->typeName;
+ output->returning.format = jtc->format;
+
+ common->pathname = NULL;
+ common->expr = jvexpr;
+ common->passing = passingArgs;
+
+ if (jtc->pathspec)
+ pathspec = jtc->pathspec;
+ else
+ {
+ /* Construct default path as '$."column_name"' */
+ StringInfoData path;
+
+ initStringInfo(&path);
+
+ appendStringInfoString(&path, "$.");
+ escape_json(&path, jtc->name);
+
+ pathspec = path.data;
+ }
+
+ common->pathspec = makeStringConst(pathspec, -1);
+
+ jvexpr->expr = (Expr *) contextItemExpr;
+ jvexpr->format.type = JS_FORMAT_DEFAULT;
+ jvexpr->format.encoding = JS_ENC_DEFAULT;
+
+ return (Node *) jfexpr;
+}
+
+static bool
+isJsonTablePathNameDuplicate(JsonTableContext *cxt, const char *pathname)
+{
+ ListCell *lc;
+
+ foreach(lc, cxt->pathNames)
+ {
+ if (!strcmp(pathname, (const char *) lfirst(lc)))
+ return true;
+ }
+
+ return false;
+}
+
+/* Recursively register column name in the path name list. */
+static void
+registerJsonTableColumn(JsonTableContext *cxt, char *colname)
+{
+ if (isJsonTablePathNameDuplicate(cxt, colname))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_ALIAS),
+ errmsg("duplicate JSON_TABLE column name: %s", colname),
+ errhint("JSON_TABLE path names and column names shall be "
+ "distinct from one another")));
+
+ cxt->pathNames = lappend(cxt->pathNames, colname);
+}
+
+/* Recursively register all nested column names in the path name list. */
+static void
+registerAllJsonTableColumns(JsonTableContext *cxt, List *columns)
+{
+ ListCell *lc;
+
+ foreach(lc, columns)
+ {
+ JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
+
+ if (jtc->coltype == JTC_NESTED)
+ {
+ if (jtc->pathname)
+ registerJsonTableColumn(cxt, jtc->pathname);
+
+ registerAllJsonTableColumns(cxt, jtc->columns);
+ }
+ else
+ {
+ registerJsonTableColumn(cxt, jtc->name);
+ }
+ }
+}
+
+/* Generate a new unique JSON_TABLE path name. */
+static char *
+generateJsonTablePathName(JsonTableContext *cxt)
+{
+ char namebuf[32];
+ char *name = namebuf;
+
+ do
+ {
+ snprintf(namebuf, sizeof(namebuf), "json_table_path_%d",
+ ++cxt->pathNameId);
+ } while (isJsonTablePathNameDuplicate(cxt, name));
+
+ name = pstrdup(name);
+ cxt->pathNames = lappend(cxt->pathNames, name);
+
+ return name;
+}
+
+/* Collect sibling path names from plan to the specified list. */
+static void
+collectSiblingPathsInJsonTablePlan(JsonTablePlan *plan, List **paths)
+{
+ if (plan->plan_type == JSTP_SIMPLE)
+ *paths = lappend(*paths, plan->pathname);
+ else if (plan->plan_type == JSTP_JOINED)
+ {
+ if (plan->join_type == JSTP_INNER ||
+ plan->join_type == JSTP_OUTER)
+ {
+ Assert(plan->plan1->plan_type == JSTP_SIMPLE);
+ *paths = lappend(*paths, plan->plan1->pathname);
+ }
+ else if (plan->join_type == JSTP_CROSS ||
+ plan->join_type == JSTP_UNION)
+ {
+ collectSiblingPathsInJsonTablePlan(plan->plan1, paths);
+ collectSiblingPathsInJsonTablePlan(plan->plan2, paths);
+ }
+ else
+ elog(ERROR, "invalid JSON_TABLE join type %d",
+ plan->join_type);
+ }
+}
+
+/*
+ * Validate child JSON_TABLE plan by checking that:
+ * - all nested columns have path names specified
+ * - all nested columns have corresponding node in the sibling plan
+ * - plan does not contain duplicate or extra nodes
+ */
+static void
+validateJsonTableChildPlan(ParseState *pstate, JsonTablePlan *plan,
+ List *columns)
+{
+ ListCell *lc1;
+ List *siblings = NIL;
+ int nchilds = 0;
+
+ if (plan)
+ collectSiblingPathsInJsonTablePlan(plan, &siblings);
+
+ foreach(lc1, columns)
+ {
+ JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc1));
+
+ if (jtc->coltype == JTC_NESTED)
+ {
+ ListCell *lc2;
+ bool found = false;
+
+ if (!jtc->pathname)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("nested JSON_TABLE columns shall contain "
+ "explicit AS pathname specification if "
+ "explicit PLAN clause is used"),
+ parser_errposition(pstate, jtc->location)));
+
+ /* find nested path name in the list of sibling path names */
+ foreach(lc2, siblings)
+ {
+ if ((found = !strcmp(jtc->pathname, lfirst(lc2))))
+ break;
+ }
+
+ if (!found)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan"),
+ errdetail("plan node for nested path %s "
+ "was not found in plan", jtc->pathname),
+ parser_errposition(pstate, jtc->location)));
+
+ nchilds++;
+ }
+ }
+
+ if (list_length(siblings) > nchilds)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan"),
+ errdetail("plan node contains some extra or "
+ "duplicate sibling nodes"),
+ parser_errposition(pstate, plan ? plan->location : -1)));
+}
+
+static JsonTableColumn *
+findNestedJsonTableColumn(List *columns, const char *pathname)
+{
+ ListCell *lc;
+
+ foreach(lc, columns)
+ {
+ JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
+
+ if (jtc->coltype == JTC_NESTED &&
+ jtc->pathname &&
+ !strcmp(jtc->pathname, pathname))
+ return jtc;
+ }
+
+ return NULL;
+}
+
+static Node *
+transformNestedJsonTableColumn(ParseState *pstate, JsonTableContext *cxt,
+ JsonTableColumn *jtc, JsonTablePlan *plan)
+{
+ JsonTableParentNode *node;
+ char *pathname = jtc->pathname;
+
+ node = transformJsonTableColumns(pstate, cxt, plan,
+ jtc->columns, jtc->pathspec,
+ &pathname, jtc->location);
+ node->name = pstrdup(pathname);
+
+ return (Node *) node;
+}
+
+static Node *
+makeJsonTableSiblingJoin(bool cross, Node *lnode, Node *rnode)
+{
+ JsonTableSiblingNode *join = makeNode(JsonTableSiblingNode);
+
+ join->larg = lnode;
+ join->rarg = rnode;
+ join->cross = cross;
+
+ return (Node *) join;
+}
+
+/*
+ * Recursively transform child JSON_TABLE plan.
+ *
+ * Default plan is transformed into a cross/union join of its nested columns.
+ * Simple and outer/inner plans are transformed into a JsonTableParentNode by
+ * finding and transforming corresponding nested column.
+ * Sibling plans are recursively transformed into a JsonTableSiblingNode.
+ */
+static Node *
+transformJsonTableChildPlan(ParseState *pstate, JsonTableContext *cxt,
+ JsonTablePlan *plan, List *columns)
+{
+ JsonTableColumn *jtc = NULL;
+
+ if (!plan || plan->plan_type == JSTP_DEFAULT)
+ {
+ /* unspecified or default plan */
+ Node *res = NULL;
+ ListCell *lc;
+ bool cross = plan && (plan->join_type & JSTP_CROSS);
+
+ /* transform all nested columns into cross/union join */
+ foreach(lc, columns)
+ {
+ JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
+ Node *node;
+
+ if (jtc->coltype != JTC_NESTED)
+ continue;
+
+ node = transformNestedJsonTableColumn(pstate, cxt, jtc, plan);
+
+ /* join transformed node with previous sibling nodes */
+ res = res ? makeJsonTableSiblingJoin(cross, res, node) : node;
+ }
+
+ return res;
+ }
+ else if (plan->plan_type == JSTP_SIMPLE)
+ {
+ jtc = findNestedJsonTableColumn(columns, plan->pathname);
+ }
+ else if (plan->plan_type == JSTP_JOINED)
+ {
+ if (plan->join_type == JSTP_INNER ||
+ plan->join_type == JSTP_OUTER)
+ {
+ Assert(plan->plan1->plan_type == JSTP_SIMPLE);
+ jtc = findNestedJsonTableColumn(columns, plan->plan1->pathname);
+ }
+ else
+ {
+ Node *node1 =
+ transformJsonTableChildPlan(pstate, cxt, plan->plan1, columns);
+ Node *node2 =
+ transformJsonTableChildPlan(pstate, cxt, plan->plan2, columns);
+
+ return makeJsonTableSiblingJoin(plan->join_type == JSTP_CROSS,
+ node1, node2);
+ }
+ }
+ else
+ elog(ERROR, "invalid JSON_TABLE plan type %d", plan->plan_type);
+
+ if (!jtc)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan"),
+ errdetail("path name was %s not found in nested columns list",
+ plan->pathname),
+ parser_errposition(pstate, plan->location)));
+
+ return transformNestedJsonTableColumn(pstate, cxt, jtc, plan);
+}
+
+/* Append transformed non-nested JSON_TABLE columns to the TableFunc node */
+static void
+appendJsonTableColumns(ParseState *pstate, JsonTableContext *cxt, List *columns)
+{
+ JsonTable *jt = cxt->table;
+ TableFunc *tf = cxt->tablefunc;
+ bool errorOnError = jt->on_error &&
+ jt->on_error->btype == JSON_BEHAVIOR_ERROR;
+ ListCell *col;
+
+ foreach(col, columns)
+ {
+ JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col));
+ Oid typid;
+ int32 typmod;
+ Node *colexpr;
+
+ if (rawc->name)
+ {
+ /* make sure column names are unique */
+ ListCell *colname;
+
+ foreach(colname, tf->colnames)
+ if (!strcmp((const char *) colname, rawc->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("column name \"%s\" is not unique",
+ rawc->name),
+ parser_errposition(pstate, rawc->location)));
+
+ tf->colnames = lappend(tf->colnames,
+ makeString(pstrdup(rawc->name)));
+ }
+
+ /*
+ * Determine the type and typmod for the new column. FOR
+ * ORDINALITY columns are INTEGER by standard; the others are
+ * user-specified.
+ */
+ switch (rawc->coltype)
+ {
+ case JTC_FOR_ORDINALITY:
+ colexpr = NULL;
+ typid = INT4OID;
+ typmod = -1;
+ break;
+
+ case JTC_REGULAR:
+ case JTC_FORMATTED:
+ {
+ Node *je;
+ CaseTestExpr *param = makeNode(CaseTestExpr);
+
+ param->collation = InvalidOid;
+ param->typeId = cxt->contextItemTypid;
+ param->typeMod = -1;
+
+ je = transformJsonTableColumn(rawc, (Node *) param,
+ NIL, errorOnError);
+
+ colexpr = transformExpr(pstate, je, EXPR_KIND_FROM_FUNCTION);
+ assign_expr_collations(pstate, colexpr);
+
+ typid = exprType(colexpr);
+ typmod = exprTypmod(colexpr);
+ break;
+ }
+
+ case JTC_NESTED:
+ continue;
+
+ default:
+ elog(ERROR, "unknown JSON_TABLE column type: %d", rawc->coltype);
+ break;
+ }
+
+ tf->coltypes = lappend_oid(tf->coltypes, typid);
+ tf->coltypmods = lappend_int(tf->coltypmods, typmod);
+ tf->colcollations = lappend_oid(tf->colcollations,
+ type_is_collatable(typid)
+ ? DEFAULT_COLLATION_OID
+ : InvalidOid);
+ tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
+ }
+}
+
+/*
+ * Create transformed JSON_TABLE parent plan node by appending all non-nested
+ * columns to the TableFunc node and remembering their indices in the
+ * colvalexprs list.
+ */
+static JsonTableParentNode *
+makeParentJsonTableNode(ParseState *pstate, JsonTableContext *cxt,
+ char *pathSpec, List *columns)
+{
+ JsonTableParentNode *node = makeNode(JsonTableParentNode);
+
+ node->path = makeConst(JSONPATHOID, -1, InvalidOid, -1,
+ DirectFunctionCall1(jsonpath_in,
+ CStringGetDatum(pathSpec)),
+ false, false);
+
+ /* save start of column range */
+ node->colMin = list_length(cxt->tablefunc->colvalexprs);
+
+ appendJsonTableColumns(pstate, cxt, columns);
+
+ /* save end of column range */
+ node->colMax = list_length(cxt->tablefunc->colvalexprs) - 1;
+
+ node->errorOnError =
+ cxt->table->on_error &&
+ cxt->table->on_error->btype == JSON_BEHAVIOR_ERROR;
+
+ return node;
+}
+
+static JsonTableParentNode *
+transformJsonTableColumns(ParseState *pstate, JsonTableContext *cxt,
+ JsonTablePlan *plan, List *columns,
+ char *pathSpec, char **pathName, int location)
+{
+ JsonTableParentNode *node;
+ JsonTablePlan *childPlan;
+ bool defaultPlan = !plan || plan->plan_type == JSTP_DEFAULT;
+
+ if (!*pathName)
+ {
+ if (cxt->table->plan)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE expression"),
+ errdetail("JSON_TABLE columns shall contain "
+ "explicit AS pathname specification if "
+ "explicit PLAN clause is used"),
+ parser_errposition(pstate, location)));
+
+ *pathName = generateJsonTablePathName(cxt);
+ }
+
+ if (defaultPlan)
+ childPlan = plan;
+ else
+ {
+ /* validate parent and child plans */
+ JsonTablePlan *parentPlan;
+
+ if (plan->plan_type == JSTP_JOINED)
+ {
+ if (plan->join_type != JSTP_INNER &&
+ plan->join_type != JSTP_OUTER)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan"),
+ errdetail("expected INNER or OUTER JSON_TABLE plan node"),
+ parser_errposition(pstate, plan->location)));
+
+ parentPlan = plan->plan1;
+ childPlan = plan->plan2;
+
+ Assert(parentPlan->plan_type != JSTP_JOINED);
+ Assert(parentPlan->pathname);
+ }
+ else
+ {
+ parentPlan = plan;
+ childPlan = NULL;
+ }
+
+ if (strcmp(parentPlan->pathname, *pathName))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan"),
+ errdetail("path name mismatch: expected %s but %s is given",
+ *pathName, parentPlan->pathname),
+ parser_errposition(pstate, plan->location)));
+
+
+ validateJsonTableChildPlan(pstate, childPlan, columns);
+ }
+
+ /* transform only non-nested columns */
+ node = makeParentJsonTableNode(pstate, cxt, pathSpec, columns);
+ node->name = pstrdup(*pathName);
+
+ if (childPlan || defaultPlan)
+ {
+ /* transform recursively nested columns */
+ node->child = transformJsonTableChildPlan(pstate, cxt, childPlan,
+ columns);
+ if (node->child)
+ node->outerJoin = !plan || (plan->join_type & JSTP_OUTER);
+ /* else: default plan case, no children found */
+ }
+
+ return node;
+}
+
+/*
+ * transformJsonTable -
+ * Transform a raw JsonTable into TableFunc.
+ *
+ * Transform the document-generating expression, the row-generating expression,
+ * the column-generating expressions, and the default value expressions.
+ */
+static RangeTblEntry *
+transformJsonTable(ParseState *pstate, JsonTable *jt)
+{
+ JsonTableContext cxt;
+ TableFunc *tf = makeNode(TableFunc);
+ JsonFuncExpr *jfe = makeNode(JsonFuncExpr);
+ JsonCommon *jscommon;
+ JsonTablePlan *plan = jt->plan;
+ char *rootPathName = jt->common->pathname;
+ char *rootPath;
+ bool is_lateral;
+
+ cxt.table = jt;
+ cxt.tablefunc = tf;
+ cxt.pathNames = NIL;
+ cxt.pathNameId = 0;
+
+ if (rootPathName)
+ registerJsonTableColumn(&cxt, rootPathName);
+
+ registerAllJsonTableColumns(&cxt, jt->columns);
+
+#if 0 /* XXX it' unclear from the standard whether root path name is mandatory or not */
+ if (plan && plan->plan_type != JSTP_DEFAULT && !rootPathName)
+ {
+ /* Assign root path name and create corresponding plan node */
+ JsonTablePlan *rootNode = makeNode(JsonTablePlan);
+ JsonTablePlan *rootPlan = (JsonTablePlan *)
+ makeJsonTableJoinedPlan(JSTP_OUTER, (Node *) rootNode,
+ (Node *) plan, jt->location);
+
+ rootPathName = generateJsonTablePathName(&cxt);
+
+ rootNode->plan_type = JSTP_SIMPLE;
+ rootNode->pathname = rootPathName;
+
+ plan = rootPlan;
+ }
+#endif
+
+ jscommon = copyObject(jt->common);
+ jscommon->pathspec = makeStringConst(pstrdup("$"), -1);
+
+ jfe->op = IS_JSON_TABLE;
+ jfe->common = jscommon;
+ jfe->on_error = jt->on_error;
+ jfe->location = jt->common->location;
+
+ /*
+ * We make lateral_only names of this level visible, whether or not the
+ * RangeTableFunc is explicitly marked LATERAL. This is needed for SQL
+ * spec compliance and seems useful on convenience grounds for all
+ * functions in FROM.
+ *
+ * (LATERAL can't nest within a single pstate level, so we don't need
+ * save/restore logic here.)
+ */
+ Assert(!pstate->p_lateral_active);
+ pstate->p_lateral_active = true;
+
+ tf->functype = TFT_JSON_TABLE;
+ tf->docexpr = transformExpr(pstate, (Node *) jfe, EXPR_KIND_FROM_FUNCTION);
+
+ cxt.contextItemTypid = exprType(tf->docexpr);
+
+ if (!IsA(jt->common->pathspec, A_Const) ||
+ castNode(A_Const, jt->common->pathspec)->val.type != T_String)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only string constants supported in JSON_TABLE path specification"),
+ parser_errposition(pstate,
+ exprLocation(jt->common->pathspec))));
+
+ rootPath = castNode(A_Const, jt->common->pathspec)->val.val.str;
+
+ tf->plan = (Node *) transformJsonTableColumns(pstate, &cxt, plan,
+ jt->columns,
+ rootPath, &rootPathName,
+ jt->common->location);
+
+ tf->ordinalitycol = -1; /* undefine ordinality column number */
+ tf->location = jt->location;
+
+ pstate->p_lateral_active = false;
+
+ /*
+ * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
+ * there are any lateral cross-references in it.
+ */
+ is_lateral = jt->lateral || contain_vars_of_level((Node *) tf, 0);
+
+ return addRangeTableEntryForTableFunc(pstate,
+ tf, jt->alias, is_lateral, true);
+}
+
/*
* transformFromClauseItem -
* Transform a FROM-clause item, adding any required entries to the
@@ -1172,6 +1839,31 @@ transformFromClauseItem(ParseState *pstate, Node *n,
rte->tablesample = transformRangeTableSample(pstate, rts);
return (Node *) rtr;
}
+ else if (IsA(n, JsonTable))
+ {
+ /* JsonTable is transformed into RangeSubselect */
+ /*
+ JsonTable *jt = castNode(JsonTable, n);
+ RangeSubselect *subselect = transformJsonTable(pstate, jt);
+
+ return transformFromClauseItem(pstate, (Node *) subselect,
+ top_rte, top_rti, namespace);
+ */
+ RangeTblRef *rtr;
+ RangeTblEntry *rte;
+ int rtindex;
+
+ rte = transformJsonTable(pstate, (JsonTable *) n);
+ /* assume new rte is at end */
+ rtindex = list_length(pstate->p_rtable);
+ Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
+ *top_rte = rte;
+ *top_rti = rtindex;
+ *namespace = list_make1(makeDefaultNSItem(rte));
+ rtr = makeNode(RangeTblRef);
+ rtr->rtindex = rtindex;
+ return (Node *) rtr;
+ }
else if (IsA(n, JoinExpr))
{
/* A newfangled join expression */
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 4d2e586bd6..c20ac2b5a9 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -667,6 +667,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 97f535a2f0..aa61ceff3c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -15,6 +15,8 @@
#include "postgres.h"
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "miscadmin.h"
@@ -34,6 +36,7 @@
#include "parser/parse_agg.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"
@@ -120,6 +123,15 @@ static Node *transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte,
static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformJsonObjectCtor(ParseState *pstate, JsonObjectCtor *ctor);
+static Node *transformJsonArrayCtor(ParseState *pstate, JsonArrayCtor *ctor);
+static Node *transformJsonArrayQueryCtor(ParseState *pstate,
+ JsonArrayQueryCtor *ctor);
+static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg);
+static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg);
+static Node *transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *p);
+static Node *transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *p);
+static Node *transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve);
static Node *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -368,6 +380,38 @@ transformExprRecurse(ParseState *pstate, Node *expr)
break;
}
+ case T_JsonObjectCtor:
+ result = transformJsonObjectCtor(pstate, (JsonObjectCtor *) expr);
+ break;
+
+ case T_JsonArrayCtor:
+ result = transformJsonArrayCtor(pstate, (JsonArrayCtor *) expr);
+ break;
+
+ case T_JsonArrayQueryCtor:
+ result = transformJsonArrayQueryCtor(pstate, (JsonArrayQueryCtor *) expr);
+ break;
+
+ case T_JsonObjectAgg:
+ result = transformJsonObjectAgg(pstate, (JsonObjectAgg *) expr);
+ break;
+
+ case T_JsonArrayAgg:
+ result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr);
+ break;
+
+ case T_JsonIsPredicate:
+ result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr);
+ break;
+
+ case T_JsonFuncExpr:
+ result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr);
+ break;
+
+ case T_JsonValueExpr:
+ result = transformJsonValueExpr(pstate, (JsonValueExpr *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3573,3 +3617,1234 @@ 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->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:
+ default:
+ enc = "UTF8";
+ 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_PG_CONVERT_FROM, TEXTOID,
+ list_make2(expr, encoding),
+ InvalidOid, InvalidOid,
+ COERCE_INTERNAL_CAST);
+
+ fexpr->location = location;
+
+ return (Node *) fexpr;
+}
+
+static Node *
+makeCaseTestExpr(Node *expr)
+{
+ CaseTestExpr *placeholder = makeNode(CaseTestExpr);
+
+ placeholder->typeId = exprType(expr);
+ placeholder->typeMod = exprTypmod(expr);
+ placeholder->collation = exprCollation(expr);
+
+ return (Node *) placeholder;
+}
+
+/*
+ * Transform JSON value expression using specified input JSON format or
+ * default format otherwise.
+ */
+static Node *
+transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve,
+ JsonFormatType default_format, bool isarg,
+ Node **prawexpr)
+{
+ Node *expr = transformExprRecurse(pstate, (Node *) ve->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");
+
+ exprtype = exprType(expr);
+ location = exprLocation(expr);
+
+ get_type_category_preferred(exprtype, &typcategory, &typispreferred);
+
+ rawexpr = expr;
+
+ if (prawexpr)
+ {
+ /*
+ * Save a raw context item expression if it is needed for the isolation
+ * of error handling in the formatting stage.
+ */
+ *prawexpr = expr;
+ assign_expr_collations(pstate, expr);
+ expr = makeCaseTestExpr(expr);
+ }
+
+ if (ve->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")));
+ }
+ else
+ format = ve->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)
+ {
+ Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
+ Node *orig = expr;
+ Node *coerced;
+ FuncExpr *fexpr;
+
+ if (!isarg && exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg(ve->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_INTERNAL_CAST,
+ location);
+
+ if (coerced)
+ expr = coerced != orig ? coerced : rawexpr;
+ else
+ {
+ /* If coercion failed, use to_json()/to_jsonb() functions. */
+ fexpr = makeFuncExpr(targettype == JSONOID ? F_TO_JSON : F_TO_JSONB,
+ targettype, list_make1(expr),
+ InvalidOid, InvalidOid,
+ COERCE_INTERNAL_CAST);
+ fexpr->location = location;
+
+ expr = (Node *) fexpr;
+ }
+
+ ve = copyObject(ve);
+ ve->expr = (Expr *) expr;
+
+ expr = (Node *) ve;
+ }
+ else
+ expr = rawexpr;
+
+ 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, NULL);
+}
+
+/*
+ * Transform JSON value expression using unspecified format by default.
+ */
+static Node *
+transformJsonValueExprDefault(ParseState *pstate, JsonValueExpr *jve)
+{
+ return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false, NULL);
+}
+
+/*
+ * 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->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->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 void
+transformJsonOutput(ParseState *pstate, const JsonOutput *output,
+ bool allow_format, JsonReturning *ret)
+{
+ /* if output clause is not specified, make default clause value */
+ if (!output)
+ {
+ ret->format.type = JS_FORMAT_DEFAULT;
+ ret->format.encoding = JS_ENC_DEFAULT;
+ ret->format.location = -1;
+ ret->typid = InvalidOid;
+ ret->typmod = -1;
+
+ return;
+ }
+
+ *ret = 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.type == JS_FORMAT_DEFAULT)
+ /* assign JSONB format when returning jsonb, or JSON format otherwise */
+ ret->format.type =
+ ret->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON;
+ else
+ checkJsonOutputFormat(pstate, &ret->format, ret->typid, allow_format);
+}
+
+/*
+ * Coerce json[b]-valued function expression to the output type.
+ */
+static Node *
+coerceJsonFuncExpr(ParseState *pstate, Node *expr, 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.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_PG_CONVERT_TO, BYTEAOID,
+ list_make2(texpr, enc),
+ InvalidOid, InvalidOid,
+ COERCE_INTERNAL_CAST);
+ fexpr->location = location;
+
+ return (Node *) fexpr;
+ }
+
+ if (returning->format.type == JS_FORMAT_JSONB &&
+ returning->typid == BYTEAOID &&
+ exprtype == JSONOID)
+ {
+ /* cast json to jsonb before encoding into bytea */
+ expr = coerce_to_specific_type(pstate, expr, JSONBOID, "JSON_FUNCTION");
+ exprtype = JSONBOID;
+ }
+
+ /* 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_INTERNAL_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 JsonCtorOpts *
+makeJsonCtorOpts(const JsonReturning *returning, bool unique,
+ bool absent_on_null)
+{
+ JsonCtorOpts *opts = makeNode(JsonCtorOpts);
+
+ opts->returning = *returning;
+ opts->unique = unique;
+ opts->absent_on_null = absent_on_null;
+
+ return opts;
+}
+
+/*
+ * 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 *
+transformJsonObjectCtor(ParseState *pstate, JsonObjectCtor *ctor)
+{
+ JsonReturning returning;
+ FuncExpr *fexpr;
+ List *args = NIL;
+ Oid funcid;
+ Oid funcrettype;
+
+ /* transform key-value pairs, if any */
+ if (ctor->exprs)
+ {
+ ListCell *lc;
+
+ /* append the first two arguments */
+ args = lappend(args, makeBoolConst(ctor->absent_on_null, false));
+ args = lappend(args, makeBoolConst(ctor->unique, false));
+
+ /* transform and append key-value arguments */
+ foreach(lc, ctor->exprs)
+ {
+ JsonKeyValue *kv = castNode(JsonKeyValue, lfirst(lc));
+ Node *key = transformExprRecurse(pstate, (Node *) kv->key);
+ Node *val = transformJsonValueExprDefault(pstate, kv->value);
+
+ args = lappend(args, key);
+ args = lappend(args, val);
+ }
+ }
+
+ transformJsonOutput(pstate, ctor->output, true, &returning);
+
+ if (returning.format.type == JS_FORMAT_JSONB)
+ {
+ funcid = args ? F_JSONB_BUILD_OBJECT_EXT : F_JSONB_BUILD_OBJECT_NOARGS;
+ funcrettype = JSONBOID;
+ }
+ else
+ {
+ funcid = args ? F_JSON_BUILD_OBJECT_EXT : F_JSON_BUILD_OBJECT_NOARGS;
+ funcrettype = JSONOID;
+ }
+
+ fexpr = makeFuncExpr(funcid, funcrettype, args,
+ InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
+ fexpr->location = ctor->location;
+ fexpr->funcformat2 = FUNCFMT_JSON_OBJECT;
+ fexpr->funcformatopts = (Node *) makeJsonCtorOpts(&returning,
+ ctor->unique,
+ ctor->absent_on_null);
+
+ return coerceJsonFuncExpr(pstate, (Node *) fexpr, &returning, true);
+}
+
+/*
+ * Transform JSON_ARRAY(query [FORMAT] [RETURNING] [ON NULL]) into
+ * (SELECT JSON_ARRAYAGG(a [FORMAT] [RETURNING] [ON NULL]) FROM (query) q(a))
+ */
+static Node *
+transformJsonArrayQueryCtor(ParseState *pstate, JsonArrayQueryCtor *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->ctor.agg_order = NIL;
+ agg->ctor.output = ctor->output;
+ agg->absent_on_null = ctor->absent_on_null;
+ agg->ctor.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 *
+transformJsonAggCtor(ParseState *pstate, JsonAggCtor *agg_ctor,
+ JsonReturning *returning, List *args, const char *aggfn,
+ Oid aggtype, FuncFormat format, JsonCtorOpts *formatopts)
+{
+ 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->winformat = format;
+ wfunc->winformatopts = (Node *) formatopts;
+ 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->aggformat = format;
+ aggref->aggformatopts = (Node *) formatopts;
+ aggref->location = agg_ctor->location;
+
+ transformAggregateCall(pstate, aggref, args, agg_ctor->agg_order, false);
+
+ node = (Node *) aggref;
+ }
+
+ return coerceJsonFuncExpr(pstate, node, returning, true);
+}
+
+/*
+ * 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;
+
+ transformJsonOutput(pstate, agg->ctor.output, true, &returning);
+
+ key = transformExprRecurse(pstate, (Node *) agg->arg->key);
+ val = transformJsonValueExprDefault(pstate, agg->arg->value);
+
+ args = list_make4(key,
+ val,
+ makeBoolConst(agg->absent_on_null, false),
+ makeBoolConst(agg->unique, false));
+
+ if (returning.format.type == JS_FORMAT_JSONB)
+ {
+ aggfnname = "pg_catalog.jsonb_objectagg"; /* F_JSONB_OBJECTAGG */
+ aggtype = JSONBOID;
+ }
+ else
+ {
+ aggfnname = "pg_catalog.json_objectagg"; /* F_JSON_OBJECTAGG; */
+ aggtype = JSONOID;
+ }
+
+ return transformJsonAggCtor(pstate, &agg->ctor, &returning, args, aggfnname,
+ aggtype, FUNCFMT_JSON_OBJECTAGG,
+ makeJsonCtorOpts(&returning,
+ 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;
+
+ transformJsonOutput(pstate, agg->ctor.output, true, &returning);
+
+ arg = transformJsonValueExprDefault(pstate, agg->arg);
+
+ if (returning.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 transformJsonAggCtor(pstate, &agg->ctor, &returning, list_make1(arg),
+ aggfnname, aggtype, FUNCFMT_JSON_ARRAYAGG,
+ makeJsonCtorOpts(&returning,
+ 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 *
+transformJsonArrayCtor(ParseState *pstate, JsonArrayCtor *ctor)
+{
+ JsonReturning returning;
+ FuncExpr *fexpr;
+ List *args = NIL;
+ Oid funcid;
+ Oid funcrettype;
+
+ /* transform element expressions, if any */
+ if (ctor->exprs)
+ {
+ ListCell *lc;
+
+ /* append the first absent_on_null argument */
+ args = lappend(args, makeBoolConst(ctor->absent_on_null, false));
+
+ /* transform and append element arguments */
+ foreach(lc, ctor->exprs)
+ {
+ JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc));
+ Node *val = transformJsonValueExprDefault(pstate, jsval);
+
+ args = lappend(args, val);
+ }
+ }
+
+ transformJsonOutput(pstate, ctor->output, true, &returning);
+
+ if (returning.format.type == JS_FORMAT_JSONB)
+ {
+ funcid = args ? F_JSONB_BUILD_ARRAY_EXT : F_JSONB_BUILD_ARRAY_NOARGS;
+ funcrettype = JSONBOID;
+ }
+ else
+ {
+ funcid = args ? F_JSON_BUILD_ARRAY_EXT : F_JSON_BUILD_ARRAY_NOARGS;
+ funcrettype = JSONOID;
+ }
+
+ fexpr = makeFuncExpr(funcid, funcrettype, args,
+ InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
+ fexpr->location = ctor->location;
+ fexpr->funcformat2 = FUNCFMT_JSON_ARRAY;
+ fexpr->funcformatopts = (Node *) makeJsonCtorOpts(&returning, false,
+ ctor->absent_on_null);
+
+ return coerceJsonFuncExpr(pstate, (Node *) fexpr, &returning, true);
+}
+
+static const char *
+JsonValueTypeStrings[] =
+{
+ "any",
+ "object",
+ "array",
+ "scalar",
+};
+
+static Const *
+makeJsonValueTypeConst(JsonValueType type)
+{
+ return makeConst(TEXTOID, -1, InvalidOid, -1,
+ PointerGetDatum(cstring_to_text(
+ JsonValueTypeStrings[(int) type])),
+ false, false);
+}
+
+/*
+ * Transform IS JSON predicate into
+ * json[b]_is_valid(json, value_type [, check_key_uniqueness]) call.
+ */
+static Node *
+transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred)
+{
+ Node *expr = transformExprRecurse(pstate, pred->expr);
+ Oid exprtype = exprType(expr);
+ FuncExpr *fexpr;
+ JsonIsPredicateOpts *opts;
+
+ /* prepare input document */
+ if (exprtype == BYTEAOID)
+ {
+ if (pred->format.type != JS_FORMAT_JSONB)
+ {
+ expr = makeJsonByteaToTextConversion(expr, &pred->format,
+ exprLocation(expr));
+ exprtype = TEXTOID;
+ }
+ }
+ 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 (pred->format.type == JS_FORMAT_JSONB)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ parser_errposition(pstate, pred->format.location),
+ errmsg("cannot use FORMAT JSONB for string input types")));
+
+ if (pred->format.encoding != JS_ENC_DEFAULT)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ parser_errposition(pstate, pred->format.location),
+ errmsg("cannot use JSON FORMAT ENCODING clause for non-bytea input types")));
+ }
+
+ expr = (Node *) makeJsonValueExpr((Expr *) expr, pred->format);
+
+ /* make resulting expression */
+ if (exprtype == TEXTOID || exprtype == JSONOID)
+ {
+ fexpr = makeFuncExpr(F_JSON_IS_VALID, BOOLOID,
+ list_make3(expr,
+ makeJsonValueTypeConst(pred->vtype),
+ makeBoolConst(pred->unique_keys, false)),
+ InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
+
+ fexpr->location = pred->location;
+ }
+ else if (exprtype == JSONBOID || exprtype == BYTEAOID)
+ {
+ /* XXX the following expressions also can be used here:
+ * jsonb_type(jsonb) = 'type' (for object and array checks)
+ * CASE jsonb_type(jsonb) WHEN ... END (for scalars checks)
+ */
+ fexpr = makeFuncExpr(F_JSONB_IS_VALID, BOOLOID,
+ list_make2(expr,
+ makeJsonValueTypeConst(pred->vtype)),
+ InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
+
+ fexpr->location = pred->location;
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot use type %s in IS JSON predicate",
+ format_type_be(exprtype))));
+ return NULL;
+ }
+
+ opts = makeNode(JsonIsPredicateOpts);
+ opts->unique_keys = pred->unique_keys;
+ opts->value_type = pred->vtype;
+
+ fexpr->funcformat2 = FUNCFMT_IS_JSON;
+ fexpr->funcformatopts = (Node *) opts;
+
+ return (Node *) fexpr;
+}
+
+/*
+ * Transform a JSON PASSING clause.
+ */
+static void
+transformJsonPassingArgs(ParseState *pstate, JsonFormatType format, List *args,
+ JsonPassing *passing)
+{
+ 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, NULL);
+
+ 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)
+{
+ JsonBehavior b;
+
+ b.btype = behavior ? behavior->btype : default_behavior;
+ b.default_expr = b.btype != JSON_BEHAVIOR_DEFAULT ? NULL :
+ transformExprRecurse(pstate, behavior->default_expr);
+
+ return b;
+}
+
+/*
+ * 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 && func->op != IS_JSON_TABLE)
+ 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 = transformJsonValueExprExt(pstate,
+ func->common->expr,
+ JS_FORMAT_JSON,
+ false,
+ &jsexpr->raw_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;
+
+ if (jsexpr->formatted_expr == jsexpr->raw_expr)
+ jsexpr->formatted_expr = NULL;
+
+ 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);
+
+ if (func->op != IS_JSON_EXISTS && func->op != IS_JSON_TABLE)
+ 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 :
+ func->op == IS_JSON_TABLE ? JSON_BEHAVIOR_EMPTY : 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 = *context_format;
+
+ if (ret->format.type == JS_FORMAT_DEFAULT)
+ is_jsonb = exprType(context_item) == JSONBOID;
+ else
+ is_jsonb = ret->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, 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, JSON_QUERY, JSON_EXISTS.
+ */
+static void
+transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
+ JsonExpr *jsexpr)
+{
+ Node *expr = jsexpr->formatted_expr ?
+ jsexpr->formatted_expr : jsexpr->raw_expr;
+
+ transformJsonOutput(pstate, func->output, false, &jsexpr->returning);
+
+ /* 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->raw_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->raw_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_INTERNAL_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, 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,
+ JsonReturning *returning, Oid contextItemTypeId)
+{
+ struct
+ {
+ JsonCoercion **coercion;
+ Oid typid;
+ } *p,
+ coercionTypids[] =
+ {
+ { &coercions->null, UNKNOWNOID },
+ { &coercions->string, TEXTOID },
+ { &coercions->numeric, NUMERICOID },
+ { &coercions->dbl, FLOAT8OID },
+ { &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);
+ Node *contextItemExpr =
+ jsexpr->formatted_expr ? jsexpr->formatted_expr : jsexpr->raw_expr;
+
+ switch (func->op)
+ {
+ case IS_JSON_VALUE:
+ transformJsonFuncExprOutput(pstate, func, jsexpr);
+
+ jsexpr->returning.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:
+ transformJsonFuncExprOutput(pstate, func, jsexpr);
+
+ jsexpr->wrapper = func->wrapper;
+ jsexpr->omit_quotes = func->omit_quotes;
+
+ break;
+
+ case IS_JSON_EXISTS:
+ jsexpr->returning.format.type = JS_FORMAT_DEFAULT;
+ jsexpr->returning.format.encoding = JS_ENC_DEFAULT;
+ jsexpr->returning.format.location = -1;
+ jsexpr->returning.typid = BOOLOID;
+ jsexpr->returning.typmod = -1;
+
+ break;
+
+ case IS_JSON_TABLE:
+ jsexpr->returning.format.type = JS_FORMAT_DEFAULT;
+ jsexpr->returning.format.encoding = JS_ENC_DEFAULT;
+ jsexpr->returning.format.location = -1;
+ jsexpr->returning.typid = exprType(contextItemExpr);
+ jsexpr->returning.typmod = -1;
+
+ break;
+ }
+
+ return (Node *) jsexpr;
+}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 77a48b039d..abf908ee51 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1708,7 +1708,8 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
- char *refname = alias ? alias->aliasname : pstrdup("xmltable");
+ char *refname = alias ? alias->aliasname :
+ pstrdup(tf->functype == TFT_XMLTABLE ? "xmltable" : "json_table");
Alias *eref;
int numaliases;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index ba470366e1..0c7993d571 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1926,6 +1926,37 @@ FigureColnameInternal(Node *node, char **name)
case T_XmlSerialize:
*name = "xmlserialize";
return 2;
+ case T_JsonObjectCtor:
+ *name = "json_object";
+ return 2;
+ case T_JsonArrayCtor:
+ case T_JsonArrayQueryCtor:
+ *name = "json_array";
+ return 2;
+ case T_JsonObjectAgg:
+ *name = "json_objectagg";
+ return 2;
+ case T_JsonArrayAgg:
+ *name = "json_arrayagg";
+ return 2;
+ case T_JsonFuncExpr:
+ /* make SQL/JSON functions act like a regular function */
+ switch (((JsonFuncExpr *) node)->op)
+ {
+ case IS_JSON_QUERY:
+ *name = "json_query";
+ return 2;
+ case IS_JSON_VALUE:
+ *name = "json_value";
+ return 2;
+ case IS_JSON_EXISTS:
+ *name = "json_exists";
+ return 2;
+ case IS_JSON_TABLE:
+ *name = "json_table";
+ return 2;
+ }
+ break;
default:
break;
}
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 4c0c258cd7..1b7f924838 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -24,7 +24,6 @@
#include "parser/gramparse.h"
#include "parser/parser.h"
-
/*
* raw_parser
* Given a query in string form, do lexical and grammatical analysis.
@@ -117,6 +116,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
case WITH:
cur_token_length = 4;
break;
+ case WITHOUT:
+ cur_token_length = 7;
+ break;
default:
return cur_token;
}
@@ -188,8 +190,22 @@ 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;
+
}
return cur_token;
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 1ff3cfea8b..4a24e8771d 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -40,11 +40,15 @@
#error -ffast-math is known to break this code
#endif
-
-static int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
+#define date_ereport(res, msg) do { \
+ if (error) \
+ { \
+ *error = true; \
+ return (res); \
+ } \
+ else \
+ ereport(ERROR, msg); \
+} while (0)
/* common code for timetypmodin and timetztypmodin */
static int32
@@ -567,9 +571,8 @@ date_mii(PG_FUNCTION_ARGS)
* Internal routines for promoting date to timestamp and timestamp with
* time zone
*/
-
-static Timestamp
-date2timestamp(DateADT dateVal)
+Timestamp
+date2timestamp_internal(DateADT dateVal, bool *error)
{
Timestamp result;
@@ -585,9 +588,9 @@ date2timestamp(DateADT dateVal)
* boundary need be checked for overflow.
*/
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
+ date_ereport(0,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
/* date is days since 2000, timestamp is microseconds since same... */
result = dateVal * USECS_PER_DAY;
@@ -597,7 +600,13 @@ date2timestamp(DateADT dateVal)
}
static TimestampTz
-date2timestamptz(DateADT dateVal)
+date2timestamp(DateADT dateVal)
+{
+ return date2timestamp_internal(dateVal, NULL);
+}
+
+TimestampTz
+date2timestamptz_internal(DateADT dateVal, int *tzp, bool *error)
{
TimestampTz result;
struct pg_tm tt,
@@ -616,16 +625,16 @@ date2timestamptz(DateADT dateVal)
* boundary need be checked for overflow.
*/
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
+ date_ereport(0,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
j2date(dateVal + POSTGRES_EPOCH_JDATE,
&(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
tm->tm_hour = 0;
tm->tm_min = 0;
tm->tm_sec = 0;
- tz = DetermineTimeZoneOffset(tm, session_timezone);
+ tz = tzp ? *tzp : DetermineTimeZoneOffset(tm, session_timezone);
result = dateVal * USECS_PER_DAY + tz * USECS_PER_SEC;
@@ -634,14 +643,20 @@ date2timestamptz(DateADT dateVal)
* of time zone, check for allowed timestamp range after adding tz.
*/
if (!IS_VALID_TIMESTAMP(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
+ date_ereport(0,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
}
return result;
}
+static TimestampTz
+date2timestamptz(DateADT dateVal)
+{
+ return date2timestamptz_internal(dateVal, NULL, NULL);
+}
+
/*
* date2timestamp_no_overflow
*
@@ -1211,7 +1226,7 @@ time_in(PG_FUNCTION_ARGS)
/* tm2time()
* Convert a tm structure to a time data type.
*/
-static int
+int
tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
{
*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1387,7 +1402,7 @@ time_scale(PG_FUNCTION_ARGS)
* have a fundamental tie together but rather a coincidence of
* implementation. - thomas
*/
-static void
+void
AdjustTimeForTypmod(TimeADT *time, int32 typmod)
{
static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -1965,7 +1980,7 @@ time_part(PG_FUNCTION_ARGS)
/* tm2timetz()
* Convert a tm structure to a time data type.
*/
-static int
+int
tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
{
result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 206576d4bd..29d753f090 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
clock, /* 12 or 24 hour clock? */
tzsign, /* +1, -1 or 0 if timezone info is absent */
tzh,
- tzm;
+ tzm,
+ ff; /* fractional precision */
} TmFromChar;
#define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,12 @@ typedef enum
DCH_Day,
DCH_Dy,
DCH_D,
+ DCH_FF1,
+ DCH_FF2,
+ DCH_FF3,
+ DCH_FF4,
+ DCH_FF5,
+ DCH_FF6,
DCH_FX, /* global suffix */
DCH_HH24,
DCH_HH12,
@@ -645,6 +653,12 @@ typedef enum
DCH_dd,
DCH_dy,
DCH_d,
+ DCH_ff1,
+ DCH_ff2,
+ DCH_ff3,
+ DCH_ff4,
+ DCH_ff5,
+ DCH_ff6,
DCH_fx,
DCH_hh24,
DCH_hh12,
@@ -745,7 +759,13 @@ static const KeyWord DCH_keywords[] = {
{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* H */
{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -794,7 +814,13 @@ static const KeyWord DCH_keywords[] = {
{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* h */
{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -895,10 +921,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
- DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+ DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
- DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+ DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
-1, DCH_y_yyy, -1, -1, -1, -1
@@ -962,6 +988,19 @@ 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
+
+#define dch_ereport(res, ...) do { \
+ if (error) { \
+ *error = true; \
+ return (res); \
+ } else { \
+ ereport(ERROR, (__VA_ARGS__)); \
+ } \
+} while (0)
/* ----------
* Functions
@@ -977,7 +1016,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
static void DCH_to_char(FormatNode *node, bool is_interval,
TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static bool DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+ bool strict, bool *error);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index);
@@ -988,14 +1028,18 @@ static const char *get_th(char *num, int type);
static char *str_numth(char *dest, char *num, int type);
static int adjust_partial_year_to_2020(int year);
static int strspace_len(char *str);
-static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
-static void from_char_set_int(int *dest, const int value, const FormatNode *node);
-static int from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
-static int from_char_parse_int(int *dest, char **src, FormatNode *node);
+static bool from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
+ bool *error);
+static bool from_char_set_int(int *dest, const int value, const FormatNode *node, bool *error);
+static int from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node, bool *error);
+static int from_char_parse_int(int *dest, char **src, FormatNode *node, bool *error);
static int seq_search(char *name, const char *const *array, int type, int max, int *len);
-static int from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
-static void do_to_timestamp(text *date_txt, text *fmt,
- struct pg_tm *tm, fsec_t *fsec);
+static int from_char_seq_search(int *dest, char **src, const char *const *array,
+ int type, int max, FormatNode *node,
+ bool *error);
+static bool do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec,
+ int *flags, bool *error);
static char *fill_str(char *str, int c, int max);
static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
static char *int_to_roman(int number);
@@ -2171,21 +2215,26 @@ strspace_len(char *str)
*
* Puke if the date mode has already been set, and the caller attempts to set
* it to a conflicting mode.
+ *
+ * If 'error' is NULL, then errors are thrown, else '*error' is set and false
+ * is returned.
*/
-static void
-from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+static bool
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *error)
{
if (mode != FROM_CHAR_DATE_NONE)
{
if (tmfc->mode == FROM_CHAR_DATE_NONE)
tmfc->mode = mode;
else if (tmfc->mode != mode)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("invalid combination of date conventions"),
- errhint("Do not mix Gregorian and ISO week date "
- "conventions in a formatting template.")));
+ dch_ereport(false,
+ errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("invalid combination of date conventions"),
+ errhint("Do not mix Gregorian and ISO week date "
+ "conventions in a formatting template."));
}
+
+ return true;
}
/*
@@ -2193,18 +2242,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
*
* Puke if the destination integer has previously been set to some other
* non-zero value.
+ *
+ * If 'error' is NULL, then errors are thrown, else '*error' is set and false
+ * is returned.
*/
-static void
-from_char_set_int(int *dest, const int value, const FormatNode *node)
+static bool
+from_char_set_int(int *dest, const int value, const FormatNode *node,
+ bool *error)
{
if (*dest != 0 && *dest != value)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("conflicting values for \"%s\" field in formatting string",
- node->key->name),
- errdetail("This value contradicts a previous setting for "
- "the same field type.")));
+ dch_ereport(false,
+ errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("conflicting values for \"%s\" field in "
+ "formatting string",
+ node->key->name),
+ errdetail("This value contradicts a previous setting "
+ "for the same field type."));
*dest = value;
+
+ return true;
}
/*
@@ -2226,9 +2282,14 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
* Note that from_char_parse_int() provides a more convenient wrapper where
* the length of the field is the same as the length of the format keyword (as
* with DD and MI).
+ *
+ * If 'error' is NULL, then errors are thrown, else '*error' is set and -1
+ * is returned.
+ *
*/
static int
-from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node,
+ bool *error)
{
long result;
char copy[DCH_MAX_ITEM_SIZ + 1];
@@ -2261,50 +2322,56 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
char *last;
if (used < len)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("source string too short for \"%s\" formatting field",
- node->key->name),
- errdetail("Field requires %d characters, but only %d "
- "remain.",
- len, used),
- errhint("If your source string is not fixed-width, try "
- "using the \"FM\" modifier.")));
+ dch_ereport(-1,
+ errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("source string too short for \"%s\" "
+ "formatting field",
+ node->key->name),
+ errdetail("Field requires %d characters, "
+ "but only %d remain.",
+ len, used),
+ errhint("If your source string is not fixed-width, "
+ "try using the \"FM\" modifier."));
errno = 0;
result = strtol(copy, &last, 10);
used = last - copy;
if (used > 0 && used < len)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("invalid value \"%s\" for \"%s\"",
- copy, node->key->name),
- errdetail("Field requires %d characters, but only %d "
- "could be parsed.", len, used),
- errhint("If your source string is not fixed-width, try "
- "using the \"FM\" modifier.")));
+ dch_ereport(-1,
+ errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("invalid value \"%s\" for \"%s\"",
+ copy, node->key->name),
+ errdetail("Field requires %d characters, "
+ "but only %d could be parsed.",
+ len, used),
+ errhint("If your source string is not fixed-width, "
+ "try using the \"FM\" modifier."));
*src += used;
}
if (*src == init)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("invalid value \"%s\" for \"%s\"",
- copy, node->key->name),
- errdetail("Value must be an integer.")));
+ dch_ereport(-1,
+ errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("invalid value \"%s\" for \"%s\"",
+ copy, node->key->name),
+ errdetail("Value must be an integer."));
if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("value for \"%s\" in source string is out of range",
- node->key->name),
- errdetail("Value must be in the range %d to %d.",
- INT_MIN, INT_MAX)));
+ dch_ereport(-1,
+ errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("value for \"%s\" in source string is out of range",
+ node->key->name),
+ errdetail("Value must be in the range %d to %d.",
+ INT_MIN, INT_MAX));
if (dest != NULL)
- from_char_set_int(dest, (int) result, node);
+ {
+ if (!from_char_set_int(dest, (int) result, node, error))
+ return -1; /* error */
+ }
+
return *src - init;
}
@@ -2318,9 +2385,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
* required length explicitly.
*/
static int
-from_char_parse_int(int *dest, char **src, FormatNode *node)
+from_char_parse_int(int *dest, char **src, FormatNode *node, bool *error)
{
- return from_char_parse_int_len(dest, src, node->key->len, node);
+ return from_char_parse_int_len(dest, src, node->key->len, node, error);
}
/* ----------
@@ -2403,11 +2470,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len)
* pointed to by 'dest', advance 'src' to the end of the part of the string
* which matched, and return the number of characters consumed.
*
- * If the string doesn't match, throw an error.
+ * If the string doesn't match, throw an error if 'error' is NULL, otherwise
+ * set '*error' and return -1.
*/
static int
-from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max,
- FormatNode *node)
+from_char_seq_search(int *dest, char **src, const char *const *array, int type,
+ int max, FormatNode *node, bool *error)
{
int len;
@@ -2419,12 +2487,12 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
Assert(max <= DCH_MAX_ITEM_SIZ);
strlcpy(copy, *src, max + 1);
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("invalid value \"%s\" for \"%s\"",
- copy, node->key->name),
- errdetail("The given value did not match any of the allowed "
- "values for this field.")));
+ dch_ereport(-1,
+ errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("invalid value \"%s\" for \"%s\"",
+ copy, node->key->name),
+ errdetail("The given value did not match any of "
+ "the allowed values for this field."));
}
*src += len;
return len;
@@ -2517,18 +2585,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
break;
- case DCH_MS: /* millisecond */
- sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+ sprintf(s, frac_fmt, (int) (frac_val)); \
+ if (S_THth(n->suffix)) \
+ str_numth(s, s, S_TH_TYPE(n->suffix)); \
s += strlen(s);
+ case DCH_FF1: /* decisecond */
+ DCH_to_char_fsec("%01d", in->fsec / 100000);
+ break;
+ case DCH_FF2: /* centisecond */
+ DCH_to_char_fsec("%02d", in->fsec / 10000);
+ break;
+ case DCH_FF3:
+ case DCH_MS: /* millisecond */
+ DCH_to_char_fsec("%03d", in->fsec / 1000);
+ break;
+ case DCH_FF4:
+ DCH_to_char_fsec("%04d", in->fsec / 100);
break;
+ case DCH_FF5:
+ DCH_to_char_fsec("%05d", in->fsec / 10);
+ break;
+ case DCH_FF6:
case DCH_US: /* microsecond */
- sprintf(s, "%06d", (int) in->fsec);
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
- s += strlen(s);
+ DCH_to_char_fsec("%06d", in->fsec);
break;
+#undef DCH_to_char_fsec
case DCH_SSSS:
sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
tm->tm_min * SECS_PER_MINUTE +
@@ -3010,13 +3092,19 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
/* ----------
* Process a string as denoted by a list of FormatNodes.
* The TmFromChar struct pointed to by 'out' is populated with the results.
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
*
* Note: we currently don't have any to_interval() function, so there
* is no need here for INVALID_FOR_INTERVAL checks.
+ *
+ * If 'error' is NULL, then errors are thrown, else '*error' is set and false
+ * is returned.
* ----------
*/
-static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+static bool
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict,
+ bool *error)
{
FormatNode *n;
char *s;
@@ -3099,7 +3187,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
continue;
}
- from_char_set_mode(out, n->key->date_mode);
+ if (!from_char_set_mode(out, n->key->date_mode, error))
+ return false; /* error */
switch (n->key->id)
{
@@ -3110,40 +3199,49 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
case DCH_P_M:
case DCH_a_m:
case DCH_p_m:
- from_char_seq_search(&value, &s, ampm_strings_long,
- ALL_UPPER, n->key->len, n);
- from_char_set_int(&out->pm, value % 2, n);
+ if (from_char_seq_search(&value, &s, ampm_strings_long,
+ ALL_UPPER, n->key->len, n,
+ error) < 0 ||
+ !from_char_set_int(&out->pm, value % 2, n, error))
+ return false; /* error */
out->clock = CLOCK_12_HOUR;
break;
case DCH_AM:
case DCH_PM:
case DCH_am:
case DCH_pm:
- from_char_seq_search(&value, &s, ampm_strings,
- ALL_UPPER, n->key->len, n);
- from_char_set_int(&out->pm, value % 2, n);
+ if (from_char_seq_search(&value, &s, ampm_strings, ALL_UPPER,
+ n->key->len, n, error) < 0 ||
+ !from_char_set_int(&out->pm, value % 2, n, error))
+ return false;
out->clock = CLOCK_12_HOUR;
break;
case DCH_HH:
case DCH_HH12:
- from_char_parse_int_len(&out->hh, &s, 2, n);
+ if (from_char_parse_int_len(&out->hh, &s, 2, n, error) < 0)
+ return false;
out->clock = CLOCK_12_HOUR;
SKIP_THth(s, n->suffix);
break;
case DCH_HH24:
- from_char_parse_int_len(&out->hh, &s, 2, n);
+ if (from_char_parse_int_len(&out->hh, &s, 2, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
case DCH_MI:
- from_char_parse_int(&out->mi, &s, n);
+ if (from_char_parse_int(&out->mi, &s, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
case DCH_SS:
- from_char_parse_int(&out->ss, &s, n);
+ if (from_char_parse_int(&out->ss, &s, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
case DCH_MS: /* millisecond */
- len = from_char_parse_int_len(&out->ms, &s, 3, n);
+ len = from_char_parse_int_len(&out->ms, &s, 3, n, error);
+ if (len < 0)
+ return false;
/*
* 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@@ -3153,8 +3251,20 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
SKIP_THth(s, n->suffix);
break;
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ out->ff = n->key->id - DCH_FF1 + 1;
+ /* fall through */
case DCH_US: /* microsecond */
- len = from_char_parse_int_len(&out->us, &s, 6, n);
+ len = from_char_parse_int_len(&out->us, &s,
+ n->key->id == DCH_US ? 6 :
+ out->ff, n, error);
+ if (len < 0)
+ return false;
out->us *= len == 1 ? 100000 :
len == 2 ? 10000 :
@@ -3165,16 +3275,17 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
SKIP_THth(s, n->suffix);
break;
case DCH_SSSS:
- from_char_parse_int(&out->ssss, &s, n);
+ if (from_char_parse_int(&out->ssss, &s, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
case DCH_tz:
case DCH_TZ:
case DCH_OF:
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("formatting field \"%s\" is only supported in to_char",
- n->key->name)));
+ dch_ereport(false,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("formatting field \"%s\" is only supported in to_char",
+ n->key->name));
break;
case DCH_TZH:
@@ -3198,82 +3309,97 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
out->tzsign = +1;
}
- from_char_parse_int_len(&out->tzh, &s, 2, n);
+ if (from_char_parse_int_len(&out->tzh, &s, 2, n, error) < 0)
+ return false;
break;
case DCH_TZM:
/* assign positive timezone sign if TZH was not seen before */
if (!out->tzsign)
out->tzsign = +1;
- from_char_parse_int_len(&out->tzm, &s, 2, n);
+ if (from_char_parse_int_len(&out->tzm, &s, 2, n, error) < 0)
+ return false;
break;
case DCH_A_D:
case DCH_B_C:
case DCH_a_d:
case DCH_b_c:
- from_char_seq_search(&value, &s, adbc_strings_long,
- ALL_UPPER, n->key->len, n);
- from_char_set_int(&out->bc, value % 2, n);
+ if (from_char_seq_search(&value, &s, adbc_strings_long,
+ ALL_UPPER, n->key->len, n,
+ error) < 0 ||
+ !from_char_set_int(&out->bc, value % 2, n, error))
+ return false;
break;
case DCH_AD:
case DCH_BC:
case DCH_ad:
case DCH_bc:
- from_char_seq_search(&value, &s, adbc_strings,
- ALL_UPPER, n->key->len, n);
- from_char_set_int(&out->bc, value % 2, n);
+ if (from_char_seq_search(&value, &s, adbc_strings, ALL_UPPER,
+ n->key->len, n, error) < 0 ||
+ !from_char_set_int(&out->bc, value % 2, n, error))
+ return false;
break;
case DCH_MONTH:
case DCH_Month:
case DCH_month:
- from_char_seq_search(&value, &s, months_full, ONE_UPPER,
- MAX_MONTH_LEN, n);
- from_char_set_int(&out->mm, value + 1, n);
+ if (from_char_seq_search(&value, &s, months_full, ONE_UPPER,
+ MAX_MONTH_LEN, n, error) < 0 ||
+ !from_char_set_int(&out->mm, value + 1, n, error))
+ return false;
break;
case DCH_MON:
case DCH_Mon:
case DCH_mon:
- from_char_seq_search(&value, &s, months, ONE_UPPER,
- MAX_MON_LEN, n);
- from_char_set_int(&out->mm, value + 1, n);
+ if (from_char_seq_search(&value, &s, months, ONE_UPPER,
+ MAX_MON_LEN, n, error) < 0 ||
+ !from_char_set_int(&out->mm, value + 1, n, error))
+ return false;
break;
case DCH_MM:
- from_char_parse_int(&out->mm, &s, n);
+ if (from_char_parse_int(&out->mm, &s, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
case DCH_DAY:
case DCH_Day:
case DCH_day:
- from_char_seq_search(&value, &s, days, ONE_UPPER,
- MAX_DAY_LEN, n);
- from_char_set_int(&out->d, value, n);
+ if (from_char_seq_search(&value, &s, days, ONE_UPPER,
+ MAX_DAY_LEN, n, error) < 0 ||
+ !from_char_set_int(&out->d, value, n, error))
+ return false;
out->d++;
break;
case DCH_DY:
case DCH_Dy:
case DCH_dy:
- from_char_seq_search(&value, &s, days, ONE_UPPER,
- MAX_DY_LEN, n);
- from_char_set_int(&out->d, value, n);
+ if (from_char_seq_search(&value, &s, days, ONE_UPPER,
+ MAX_DY_LEN, n, error) < 0 ||
+ !from_char_set_int(&out->d, value, n, error))
+ return false;
out->d++;
break;
case DCH_DDD:
- from_char_parse_int(&out->ddd, &s, n);
+ if (from_char_parse_int(&out->ddd, &s, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
case DCH_IDDD:
- from_char_parse_int_len(&out->ddd, &s, 3, n);
+ if (from_char_parse_int_len(&out->ddd, &s, 3, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
case DCH_DD:
- from_char_parse_int(&out->dd, &s, n);
+ if (from_char_parse_int(&out->dd, &s, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
case DCH_D:
- from_char_parse_int(&out->d, &s, n);
+ if (from_char_parse_int(&out->d, &s, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
case DCH_ID:
- from_char_parse_int_len(&out->d, &s, 1, n);
+ if (from_char_parse_int_len(&out->d, &s, 1, n, error) < 0)
+ return false;
/* Shift numbering to match Gregorian where Sunday = 1 */
if (++out->d > 7)
out->d = 1;
@@ -3281,7 +3407,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
break;
case DCH_WW:
case DCH_IW:
- from_char_parse_int(&out->ww, &s, n);
+ if (from_char_parse_int(&out->ww, &s, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
case DCH_Q:
@@ -3296,11 +3423,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
* We still parse the source string for an integer, but it
* isn't stored anywhere in 'out'.
*/
- from_char_parse_int((int *) NULL, &s, n);
+ if (from_char_parse_int((int *) NULL, &s, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
case DCH_CC:
- from_char_parse_int(&out->cc, &s, n);
+ if (from_char_parse_int(&out->cc, &s, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
case DCH_Y_YYY:
@@ -3312,11 +3441,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
if (matched < 2)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("invalid input string for \"Y,YYY\"")));
+ dch_ereport(false,
+ errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("invalid input string for \"Y,YYY\""));
years += (millennia * 1000);
- from_char_set_int(&out->year, years, n);
+ if (!from_char_set_int(&out->year, years, n, error))
+ return false;
out->yysz = 4;
s += nch;
SKIP_THth(s, n->suffix);
@@ -3324,47 +3454,63 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
break;
case DCH_YYYY:
case DCH_IYYY:
- from_char_parse_int(&out->year, &s, n);
+ if (from_char_parse_int(&out->year, &s, n, error) < 0)
+ return false;
out->yysz = 4;
SKIP_THth(s, n->suffix);
break;
case DCH_YYY:
case DCH_IYY:
- if (from_char_parse_int(&out->year, &s, n) < 4)
+ len = from_char_parse_int(&out->year, &s, n, error);
+ if (len < 0)
+ return false;
+ if (len < 4)
out->year = adjust_partial_year_to_2020(out->year);
out->yysz = 3;
SKIP_THth(s, n->suffix);
break;
case DCH_YY:
case DCH_IY:
- if (from_char_parse_int(&out->year, &s, n) < 4)
+ len = from_char_parse_int(&out->year, &s, n, error);
+ if (len < 0)
+ return false;
+ if (len < 4)
out->year = adjust_partial_year_to_2020(out->year);
out->yysz = 2;
SKIP_THth(s, n->suffix);
break;
case DCH_Y:
case DCH_I:
- if (from_char_parse_int(&out->year, &s, n) < 4)
+ len = from_char_parse_int(&out->year, &s, n, error);
+ if (len < 0)
+ return false;
+ if (len < 4)
out->year = adjust_partial_year_to_2020(out->year);
out->yysz = 1;
SKIP_THth(s, n->suffix);
break;
case DCH_RM:
- from_char_seq_search(&value, &s, rm_months_upper,
- ALL_UPPER, MAX_RM_LEN, n);
- from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+ if (from_char_seq_search(&value, &s, rm_months_upper,
+ ALL_UPPER, MAX_RM_LEN, n, error) < 0 ||
+ !from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n,
+ error))
+ return false;
break;
case DCH_rm:
- from_char_seq_search(&value, &s, rm_months_lower,
- ALL_LOWER, MAX_RM_LEN, n);
- from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+ if (from_char_seq_search(&value, &s, rm_months_lower,
+ ALL_LOWER, MAX_RM_LEN, n, error) < 0 ||
+ !from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n,
+ error))
+ return false;
break;
case DCH_W:
- from_char_parse_int(&out->w, &s, n);
+ if (from_char_parse_int(&out->w, &s, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
case DCH_J:
- from_char_parse_int(&out->j, &s, n);
+ if (from_char_parse_int(&out->j, &s, n, error) < 0)
+ return false;
SKIP_THth(s, n->suffix);
break;
}
@@ -3380,6 +3526,25 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
}
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ dch_ereport(false,
+ errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format"));
+
+ while (*s != '\0' && isspace((unsigned char) *s))
+ s++;
+
+ if (*s != '\0')
+ dch_ereport(false,
+ errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string "
+ "after datetime format"));
+ }
+
+ return true;
}
/*
@@ -3400,6 +3565,115 @@ DCH_prevent_counter_overflow(void)
}
}
+/*
+ * Get mask of date/time/zone components present in format nodes.
+ *
+ * If 'error' is NULL, then errors are thrown, else '*error' is set and 0
+ * is returned.
+ */
+static int
+DCH_datetime_type(FormatNode *node, bool *error)
+{
+ FormatNode *n;
+ int flags = 0;
+
+ for (n = node; n->type != NODE_TYPE_END; n++)
+ {
+ if (n->type != NODE_TYPE_ACTION)
+ continue;
+
+ switch (n->key->id)
+ {
+ case DCH_FX:
+ break;
+ case DCH_A_M:
+ case DCH_P_M:
+ case DCH_a_m:
+ case DCH_p_m:
+ case DCH_AM:
+ case DCH_PM:
+ case DCH_am:
+ case DCH_pm:
+ case DCH_HH:
+ case DCH_HH12:
+ case DCH_HH24:
+ case DCH_MI:
+ case DCH_SS:
+ case DCH_MS: /* millisecond */
+ case DCH_US: /* microsecond */
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ case DCH_SSSS:
+ flags |= DCH_TIMED;
+ break;
+ case DCH_tz:
+ case DCH_TZ:
+ case DCH_OF:
+ dch_ereport(0,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("formatting field \"%s\" is only "
+ "supported in to_char",
+ n->key->name));
+ flags |= DCH_ZONED;
+ break;
+ case DCH_TZH:
+ case DCH_TZM:
+ flags |= DCH_ZONED;
+ break;
+ case DCH_A_D:
+ case DCH_B_C:
+ case DCH_a_d:
+ case DCH_b_c:
+ case DCH_AD:
+ case DCH_BC:
+ case DCH_ad:
+ case DCH_bc:
+ case DCH_MONTH:
+ case DCH_Month:
+ case DCH_month:
+ case DCH_MON:
+ case DCH_Mon:
+ case DCH_mon:
+ case DCH_MM:
+ case DCH_DAY:
+ case DCH_Day:
+ case DCH_day:
+ case DCH_DY:
+ case DCH_Dy:
+ case DCH_dy:
+ case DCH_DDD:
+ case DCH_IDDD:
+ case DCH_DD:
+ case DCH_D:
+ case DCH_ID:
+ case DCH_WW:
+ case DCH_Q:
+ case DCH_CC:
+ case DCH_Y_YYY:
+ case DCH_YYYY:
+ case DCH_IYYY:
+ case DCH_YYY:
+ case DCH_IYY:
+ case DCH_YY:
+ case DCH_IY:
+ case DCH_Y:
+ case DCH_I:
+ case DCH_RM:
+ case DCH_rm:
+ case DCH_W:
+ case DCH_J:
+ flags |= DCH_DATED;
+ break;
+ }
+ }
+
+ return flags;
+}
+
/* select a DCHCacheEntry to hold the given format picture */
static DCHCacheEntry *
DCH_cache_getnew(const char *str)
@@ -3672,6 +3946,72 @@ interval_to_char(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(res);
}
+/*
+ * Decode the specified or default timezone, if any, or use the session
+ * timezone if it is allowed.
+ */
+static bool
+get_timezone(struct pg_tm *tm, char *default_tz_name, text *date_txt,
+ const char *type_name, bool allow_session_timezone,
+ int *tz, bool *error)
+{
+ /* Use the specified or default time zone, if any. */
+ char *tz_name =
+ tm->tm_zone ? unconstify(char *, tm->tm_zone) : default_tz_name;
+
+ if (tz_name)
+ {
+ int dterr = DecodeTimezone(tz_name, tz);
+
+ if (dterr)
+ {
+ if (error)
+ {
+ *error = true;
+ return false;
+ }
+
+ DateTimeParseError(dterr,
+ date_txt ? text_to_cstring(date_txt) : tz_name,
+ type_name);
+ }
+ }
+ else if (*tz == PG_INT32_MIN)
+ {
+ if (!allow_session_timezone)
+ dch_ereport(false,
+ errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in input string for type %s",
+ type_name));
+
+ *tz = DetermineTimeZoneOffset(tm, session_timezone);
+ }
+
+ return true;
+}
+
+/*
+ * Convert pg_tm to timestamp ('tz' is NULL) or timestamptz ('tz' is not NULL)
+ * using the specified fractional precision 'typmod'.
+ */
+static inline Timestamp
+tm_to_timestamp(struct pg_tm *tm, fsec_t fsec, int32 typmod, int *tz,
+ bool *error)
+{
+ Timestamp result;
+
+ if (tm2timestamp(tm, fsec, tz, &result) != 0)
+ dch_ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ /* Use the specified fractional precision, if any. */
+ if (typmod != -1)
+ AdjustTimestampForTypmodError(&result, typmod, error);
+
+ return result;
+}
+
/* ---------------------
* TO_TIMESTAMP()
*
@@ -3684,30 +4024,45 @@ to_timestamp(PG_FUNCTION_ARGS)
{
text *date_txt = PG_GETARG_TEXT_PP(0);
text *fmt = PG_GETARG_TEXT_PP(1);
- Timestamp result;
- int tz;
+ int tz = PG_INT32_MIN;
struct pg_tm tm;
fsec_t fsec;
+ int fprec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL);
/* Use the specified time zone, if any. */
- if (tm.tm_zone)
- {
- int dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), &tz);
+ get_timezone(&tm, NULL, date_txt, "timestamp", true, &tz, NULL);
- if (dterr)
- DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
- }
- else
- tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ PG_RETURN_DATUM(tm_to_timestamp(&tm, fsec, fprec ? fprec : -1, &tz, NULL));
+}
- if (tm2timestamp(&tm, fsec, &tz, &result) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+/*
+ * Convert pg_tm to date with out of range checking.
+ */
+static DateADT
+tm_to_date(struct pg_tm *tm, text *date_txt, bool *error)
+{
+ DateADT result;
+
+ /* Prevent overflow in Julian-day routines */
+ if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
+ dch_ereport((Datum) 0,
+ errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt)));
+
+ result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) -
+ POSTGRES_EPOCH_JDATE;
+
+ /* Now check for just-out-of-range dates */
+ if (!IS_VALID_DATE(result))
+ dch_ereport((Datum) 0,
+ errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt)));
- PG_RETURN_TIMESTAMP(result);
+ return DateADTGetDatum(result);
}
/* ----------
@@ -3720,36 +4075,149 @@ to_date(PG_FUNCTION_ARGS)
{
text *date_txt = PG_GETARG_TEXT_PP(0);
text *fmt = PG_GETARG_TEXT_PP(1);
- DateADT result;
struct pg_tm tm;
fsec_t fsec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL);
- /* Prevent overflow in Julian-day routines */
- if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range: \"%s\"",
- text_to_cstring(date_txt))));
+ PG_RETURN_DATUM(tm_to_date(&tm, date_txt, NULL));
+}
+
+/*
+ * Convert pg_tm to timetz using the specified fractional precision 'typmod'
+ * and timezone 'tz'.
+ */
+static Datum
+tm_to_timetz(struct pg_tm *tm, fsec_t fsec, int32 typmod, int *tz, bool *error)
+{
+ TimeTzADT *result = palloc(sizeof(TimeTzADT));
- result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE;
+ if (tm2timetz(tm, fsec, *tz, result) != 0)
+ dch_ereport((Datum) 0,
+ errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timetz out of range"));
- /* Now check for just-out-of-range dates */
- if (!IS_VALID_DATE(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range: \"%s\"",
- text_to_cstring(date_txt))));
+ if (typmod != -1)
+ AdjustTimeForTypmod(&result->time, typmod);
+
+ return TimeTzADTPGetDatum(result);
+}
+
+/*
+ * Convert pg_tm to time using the specified fractional precision 'typmod'.
+ */
+static Datum
+tm_to_time(struct pg_tm *tm, fsec_t fsec, int32 typmod, bool *error)
+{
+ TimeADT result;
+
+ if (tm2time(tm, fsec, &result) != 0)
+ dch_ereport((Datum) 0,
+ errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range"));
+
+ if (typmod != -1)
+ AdjustTimeForTypmod(&result, typmod);
+
+ return TimeADTGetDatum(result);
+}
+
+/*
+ * Make datetime type from 'date_txt' which is formated at argument 'fmt'.
+ * Actual datatype (returned in 'typid', 'typmod') is determined by
+ * presence of date/time/zone components in the format string.
+ *
+ * Default time-zone for tz types is specified with 'tzname'. If 'tzname' is
+ * NULL and the input string does not contain zone components then "missing tz"
+ * error is thrown.
+ *
+ * If 'error' is NULL, then errors are thrown, else '*error' is set and
+ * ((Datum) 0) is returned.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict,
+ Oid *typid, int32 *typmod, int *tz, bool *error)
+{
+ struct pg_tm tm;
+ fsec_t fsec;
+ int fprec = 0;
+ int flags;
+
+ if (!do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags,
+ error))
+ return (Datum) 0;
+
+ /* Save default time-zone for non-zoned types. */
+ if (!(flags & DCH_ZONED) && tzname &&
+ !get_timezone(&tm, tzname, NULL, "timezone", false, tz, error))
+ return (Datum) 0;
+
+ if (flags & DCH_DATED)
+ {
+ if (flags & DCH_TIMED)
+ {
+ *typmod = fprec ? fprec : -1; /* fractional part precision */
+
+ if (flags & DCH_ZONED)
+ {
+ if (!get_timezone(&tm, tzname, NULL, "timestamptz", false, tz,
+ error))
+ return (Datum) 0;
+
+ *typid = TIMESTAMPTZOID;
+ return tm_to_timestamp(&tm, fsec, *typmod, tz, error);
+ }
+ else
+ {
+ *typid = TIMESTAMPOID;
+ return tm_to_timestamp(&tm, fsec, *typmod, NULL, error);
+ }
+ }
+ else
+ {
+ if (flags & DCH_ZONED)
+ dch_ereport((Datum) 0,
+ errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is zoned but not timed"));
+
+ *typid = DATEOID;
+ *typmod = -1;
+ return tm_to_date(&tm, date_txt, error);
+ }
+ }
+ else if (flags & DCH_TIMED)
+ {
+ *typmod = fprec ? fprec : -1; /* fractional part precision */
+
+ if (flags & DCH_ZONED)
+ {
+ if (!get_timezone(&tm, tzname, NULL, "timetz", false, tz, error))
+ return (Datum) 0;
- PG_RETURN_DATEADT(result);
+ *typid = TIMETZOID;
+ return tm_to_timetz(&tm, fsec, *typmod, tz, error);
+ }
+ else
+ {
+ *typid = TIMEOID;
+ return tm_to_time(&tm, fsec, *typmod, error);
+ }
+ }
+ else
+ {
+ dch_ereport((Datum) 0,
+ errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is not dated and not timed"));
+ }
+
+ return (Datum) 0;
}
/*
* do_to_timestamp: shared code for to_timestamp and to_date
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * and fractional seconds and fractional precision.
*
* We parse 'fmt' into a list of FormatNodes, which is then passed to
* DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3757,10 +4225,18 @@ to_date(PG_FUNCTION_ARGS)
*
* The TmFromChar is then analysed and converted into the final results in
* struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
+ *
+ * If 'error' is NULL, then errors are thrown, else '*error' is set and false
+ * is returned.
*/
-static void
-do_to_timestamp(text *date_txt, text *fmt,
- struct pg_tm *tm, fsec_t *fsec)
+static bool
+do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm,
+ fsec_t *fsec, int *fprec, int *flags, bool *error)
{
FormatNode *format;
TmFromChar tmfc;
@@ -3813,11 +4289,18 @@ do_to_timestamp(text *date_txt, text *fmt,
/* dump_index(DCH_keywords, DCH_index); */
#endif
- DCH_from_char(format, date_str, &tmfc);
+ DCH_from_char(format, date_str, &tmfc, strict, error);
pfree(fmt_str);
+
+ if (flags && (!error || !*error))
+ *flags = DCH_datetime_type(format, error);
+
if (!incache)
pfree(format);
+
+ if (error && *error)
+ goto err;
}
DEBUG_TMFC(&tmfc);
@@ -3846,11 +4329,15 @@ do_to_timestamp(text *date_txt, text *fmt,
if (tmfc.clock == CLOCK_12_HOUR)
{
if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
+ {
+ if (error)
+ goto err;
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("hour \"%d\" is invalid for the 12-hour clock",
tm->tm_hour),
errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
+ }
if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
tm->tm_hour += HOURS_PER_DAY / 2;
@@ -3954,9 +4441,13 @@ do_to_timestamp(text *date_txt, text *fmt,
*/
if (!tm->tm_year && !tmfc.bc)
+ {
+ if (error)
+ goto err;
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("cannot calculate day of year without year information")));
+ }
if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
{
@@ -3997,6 +4488,8 @@ do_to_timestamp(text *date_txt, text *fmt,
*fsec += tmfc.ms * 1000;
if (tmfc.us)
*fsec += tmfc.us;
+ if (fprec)
+ *fprec = tmfc.ff; /* fractional precision, if specified */
/* Range-check date fields according to bit mask computed above */
if (fmask != 0)
@@ -4006,6 +4499,9 @@ do_to_timestamp(text *date_txt, text *fmt,
if (dterr != 0)
{
+ if (error)
+ goto err;
+
/*
* Force the error to be DTERR_FIELD_OVERFLOW even if ValidateDate
* said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
@@ -4020,7 +4516,12 @@ do_to_timestamp(text *date_txt, text *fmt,
tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
+ {
+ if (error)
+ goto err;
+
DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+ }
/* Save parsed time-zone into tm->tm_zone if it was specified */
if (tmfc.tzsign)
@@ -4029,7 +4530,12 @@ do_to_timestamp(text *date_txt, text *fmt,
if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
+ {
+ if (error)
+ goto err;
+
DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
+ }
tz = psprintf("%c%02d:%02d",
tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@@ -4040,6 +4546,12 @@ do_to_timestamp(text *date_txt, text *fmt,
DEBUG_TM(tm);
pfree(date_str);
+ return true;
+
+err:
+ *error = true;
+ pfree(date_str);
+ return false;
}
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 26d293709a..468d6a87c2 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 "access/htup_details.h"
#include "access/transam.h"
#include "catalog/pg_type.h"
@@ -66,6 +67,23 @@ typedef enum /* type categories for datum_to_json */
JSONTYPE_OTHER /* all else */
} JsonTypeCategory;
+/* Context for key uniqueness check */
+typedef struct JsonUniqueCheckContext
+{
+ struct JsonKeyInfo
+ {
+ int offset; /* key offset:
+ * in result if positive,
+ * in skipped_keys if negative */
+ int length; /* key length */
+ } *keys; /* key info array */
+ int nkeys; /* number of processed keys */
+ int nallocated; /* number of allocated keys in array */
+ StringInfo result; /* resulting json */
+ StringInfoData skipped_keys; /* skipped keys with NULL values */
+ MemoryContext mcxt; /* context for saving skipped keys */
+} JsonUniqueCheckContext;
+
typedef struct JsonAggState
{
StringInfo str;
@@ -73,16 +91,31 @@ typedef struct JsonAggState
Oid key_output_func;
JsonTypeCategory val_category;
Oid val_output_func;
+ JsonUniqueCheckContext unique_check;
} JsonAggState;
-static inline void json_lex(JsonLexContext *lex);
+/* Element of object stack for key uniqueness check */
+typedef struct JsonObjectFields
+{
+ struct JsonObjectFields *parent;
+ HTAB *fields;
+} JsonObjectFields;
+
+/* State for key uniqueness check */
+typedef struct JsonUniqueState
+{
+ JsonLexContext *lex;
+ JsonObjectFields *stack;
+} JsonUniqueState;
+
+static inline bool json_lex(JsonLexContext *lex);
static inline void json_lex_string(JsonLexContext *lex);
static inline void json_lex_number(JsonLexContext *lex, char *s,
bool *num_err, int *total_len);
static inline void parse_scalar(JsonLexContext *lex, JsonSemAction *sem);
-static void parse_object_field(JsonLexContext *lex, JsonSemAction *sem);
+static bool parse_object_field(JsonLexContext *lex, JsonSemAction *sem);
static void parse_object(JsonLexContext *lex, JsonSemAction *sem);
-static void parse_array_element(JsonLexContext *lex, JsonSemAction *sem);
+static bool parse_array_element(JsonLexContext *lex, JsonSemAction *sem);
static void parse_array(JsonLexContext *lex, JsonSemAction *sem);
static void report_parse_error(JsonParseContext ctx, JsonLexContext *lex) pg_attribute_noreturn();
static void report_invalid_token(JsonLexContext *lex) pg_attribute_noreturn();
@@ -106,6 +139,9 @@ static void add_json(Datum val, bool is_null, StringInfo result,
Oid val_type, bool key_scalar);
static text *catenate_stringinfo_string(StringInfo buffer, const char *addon);
+static JsonIterator *JsonIteratorInitFromLex(JsonContainer *jc,
+ JsonLexContext *lex, JsonIterator *parent);
+
/* the null action object used for pure validation */
static JsonSemAction nullSemAction =
{
@@ -126,6 +162,37 @@ lex_peek(JsonLexContext *lex)
return lex->token_type;
}
+/*
+ * lex_peek_value
+ *
+ * get the current look_ahead de-escaped lexeme.
+*/
+static inline char *
+lex_peek_value(JsonLexContext *lex)
+{
+ if (lex->token_type == JSON_TOKEN_STRING)
+ return lex->strval ? pstrdup(lex->strval->data) : NULL;
+ else
+ {
+ int len = lex->token_terminator - lex->token_start;
+ char *tokstr = palloc(len + 1);
+
+ memcpy(tokstr, lex->token_start, len);
+ tokstr[len] = '\0';
+ return tokstr;
+ }
+}
+
+static inline bool
+lex_set_error(JsonLexContext *lex)
+{
+ if (lex->throw_errors)
+ return false;
+
+ lex->error = true;
+ return true;
+}
+
/*
* lex_accept
*
@@ -141,24 +208,9 @@ lex_accept(JsonLexContext *lex, JsonTokenType token, char **lexeme)
if (lex->token_type == token)
{
if (lexeme != NULL)
- {
- if (lex->token_type == JSON_TOKEN_STRING)
- {
- if (lex->strval != NULL)
- *lexeme = pstrdup(lex->strval->data);
- }
- else
- {
- int len = (lex->token_terminator - lex->token_start);
- char *tokstr = palloc(len + 1);
+ *lexeme = lex_peek_value(lex);
- memcpy(tokstr, lex->token_start, len);
- tokstr[len] = '\0';
- *lexeme = tokstr;
- }
- }
- json_lex(lex);
- return true;
+ return json_lex(lex);
}
return false;
}
@@ -169,11 +221,16 @@ lex_accept(JsonLexContext *lex, JsonTokenType token, char **lexeme)
* move the lexer to the next token if the current look_ahead token matches
* the parameter token. Otherwise, report an error.
*/
-static inline void
+static inline bool
lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token)
{
- if (!lex_accept(lex, token, NULL))
+ if (lex_accept(lex, token, NULL))
+ return true;
+
+ if (!lex_set_error(lex))
report_parse_error(ctx, lex);
+
+ return false;
}
/* chars to consider as part of an alphanumeric token */
@@ -233,7 +290,7 @@ json_in(PG_FUNCTION_ARGS)
/* validate it */
lex = makeJsonLexContext(result, false);
- pg_parse_json(lex, &nullSemAction);
+ (void) pg_parse_json(lex, &nullSemAction);
/* Internal representation is the same as text, for now */
PG_RETURN_TEXT_P(result);
@@ -280,7 +337,7 @@ json_recv(PG_FUNCTION_ARGS)
/* Validate it. */
lex = makeJsonLexContextCstringLen(str, nbytes, false);
- pg_parse_json(lex, &nullSemAction);
+ (void) pg_parse_json(lex, &nullSemAction);
PG_RETURN_TEXT_P(cstring_to_text_with_len(str, nbytes));
}
@@ -313,6 +370,7 @@ makeJsonLexContextCstringLen(char *json, int len, bool need_escapes)
lex->input = lex->token_terminator = lex->line_start = json;
lex->line_number = 1;
lex->input_length = len;
+ lex->throw_errors = true;
if (need_escapes)
lex->strval = makeStringInfo();
return lex;
@@ -328,13 +386,14 @@ makeJsonLexContextCstringLen(char *json, int len, bool need_escapes)
* action routines to be called at appropriate spots during parsing, and a
* pointer to a state object to be passed to those routines.
*/
-void
+bool
pg_parse_json(JsonLexContext *lex, JsonSemAction *sem)
{
JsonTokenType tok;
/* get the initial token */
- json_lex(lex);
+ if (!json_lex(lex))
+ return false;
tok = lex_peek(lex);
@@ -351,8 +410,7 @@ pg_parse_json(JsonLexContext *lex, JsonSemAction *sem)
parse_scalar(lex, sem); /* json can be a bare scalar */
}
- lex_expect(JSON_PARSE_END, lex, JSON_TOKEN_END);
-
+ return !lex->error && lex_expect(JSON_PARSE_END, lex, JSON_TOKEN_END);
}
/*
@@ -377,6 +435,7 @@ json_count_array_elements(JsonLexContext *lex)
memcpy(©lex, lex, sizeof(JsonLexContext));
copylex.strval = NULL; /* not interested in values here */
copylex.lex_level++;
+ copylex.throw_errors = true;
count = 0;
lex_expect(JSON_PARSE_ARRAY_START, ©lex, JSON_TOKEN_ARRAY_START);
@@ -432,14 +491,16 @@ parse_scalar(JsonLexContext *lex, JsonSemAction *sem)
lex_accept(lex, JSON_TOKEN_STRING, valaddr);
break;
default:
- report_parse_error(JSON_PARSE_VALUE, lex);
+ if (!lex_set_error(lex))
+ report_parse_error(JSON_PARSE_VALUE, lex);
+ return;
}
- if (sfunc != NULL)
+ if (sfunc != NULL && !lex->error)
(*sfunc) (sem->semstate, val, tok);
}
-static void
+static bool
parse_object_field(JsonLexContext *lex, JsonSemAction *sem)
{
/*
@@ -459,16 +520,26 @@ parse_object_field(JsonLexContext *lex, JsonSemAction *sem)
fnameaddr = &fname;
if (!lex_accept(lex, JSON_TOKEN_STRING, fnameaddr))
- report_parse_error(JSON_PARSE_STRING, lex);
+ {
+ if (!lex_set_error(lex))
+ report_parse_error(JSON_PARSE_STRING, lex);
+ return false;
+ }
- lex_expect(JSON_PARSE_OBJECT_LABEL, lex, JSON_TOKEN_COLON);
+ if (!lex_expect(JSON_PARSE_OBJECT_LABEL, lex, JSON_TOKEN_COLON))
+ return false;
tok = lex_peek(lex);
isnull = tok == JSON_TOKEN_NULL;
if (ostart != NULL)
+ {
(*ostart) (sem->semstate, fname, isnull);
+ if (lex->error)
+ return false;
+ }
+
switch (tok)
{
case JSON_TOKEN_OBJECT_START:
@@ -481,8 +552,13 @@ parse_object_field(JsonLexContext *lex, JsonSemAction *sem)
parse_scalar(lex, sem);
}
+ if (lex->error)
+ return false;
+
if (oend != NULL)
(*oend) (sem->semstate, fname, isnull);
+
+ return !lex->error;
}
static void
@@ -499,8 +575,13 @@ parse_object(JsonLexContext *lex, JsonSemAction *sem)
check_stack_depth();
if (ostart != NULL)
+ {
(*ostart) (sem->semstate);
+ if (lex->error)
+ return;
+ }
+
/*
* Data inside an object is at a higher nesting level than the object
* itself. Note that we increment this after we call the semantic routine
@@ -510,24 +591,30 @@ parse_object(JsonLexContext *lex, JsonSemAction *sem)
lex->lex_level++;
/* we know this will succeed, just clearing the token */
- lex_expect(JSON_PARSE_OBJECT_START, lex, JSON_TOKEN_OBJECT_START);
+ if (!lex_expect(JSON_PARSE_OBJECT_START, lex, JSON_TOKEN_OBJECT_START))
+ return;
tok = lex_peek(lex);
switch (tok)
{
case JSON_TOKEN_STRING:
- parse_object_field(lex, sem);
+ if (!parse_object_field(lex, sem))
+ return;
while (lex_accept(lex, JSON_TOKEN_COMMA, NULL))
- parse_object_field(lex, sem);
+ if (!parse_object_field(lex, sem))
+ return;
break;
case JSON_TOKEN_OBJECT_END:
break;
default:
/* case of an invalid initial token inside the object */
- report_parse_error(JSON_PARSE_OBJECT_START, lex);
+ if (!lex_set_error(lex))
+ report_parse_error(JSON_PARSE_OBJECT_START, lex);
+ return;
}
- lex_expect(JSON_PARSE_OBJECT_NEXT, lex, JSON_TOKEN_OBJECT_END);
+ if (!lex_expect(JSON_PARSE_OBJECT_NEXT, lex, JSON_TOKEN_OBJECT_END))
+ return;
lex->lex_level--;
@@ -535,7 +622,7 @@ parse_object(JsonLexContext *lex, JsonSemAction *sem)
(*oend) (sem->semstate);
}
-static void
+static bool
parse_array_element(JsonLexContext *lex, JsonSemAction *sem)
{
json_aelem_action astart = sem->array_element_start;
@@ -547,8 +634,13 @@ parse_array_element(JsonLexContext *lex, JsonSemAction *sem)
isnull = tok == JSON_TOKEN_NULL;
if (astart != NULL)
+ {
(*astart) (sem->semstate, isnull);
+ if (lex->error)
+ return false;
+ }
+
/* an array element is any object, array or scalar */
switch (tok)
{
@@ -562,8 +654,13 @@ parse_array_element(JsonLexContext *lex, JsonSemAction *sem)
parse_scalar(lex, sem);
}
+ if (lex->error)
+ return false;
+
if (aend != NULL)
(*aend) (sem->semstate, isnull);
+
+ return !lex->error;
}
static void
@@ -579,8 +676,13 @@ parse_array(JsonLexContext *lex, JsonSemAction *sem)
check_stack_depth();
if (astart != NULL)
+ {
(*astart) (sem->semstate);
+ if (lex->error)
+ return;
+ }
+
/*
* Data inside an array is at a higher nesting level than the array
* itself. Note that we increment this after we call the semantic routine
@@ -589,17 +691,21 @@ parse_array(JsonLexContext *lex, JsonSemAction *sem)
*/
lex->lex_level++;
- lex_expect(JSON_PARSE_ARRAY_START, lex, JSON_TOKEN_ARRAY_START);
+ if (!lex_expect(JSON_PARSE_ARRAY_START, lex, JSON_TOKEN_ARRAY_START))
+ return;
+
if (lex_peek(lex) != JSON_TOKEN_ARRAY_END)
{
-
- parse_array_element(lex, sem);
+ if (!parse_array_element(lex, sem))
+ return;
while (lex_accept(lex, JSON_TOKEN_COMMA, NULL))
- parse_array_element(lex, sem);
+ if (!parse_array_element(lex, sem))
+ return;
}
- lex_expect(JSON_PARSE_ARRAY_NEXT, lex, JSON_TOKEN_ARRAY_END);
+ if (!lex_expect(JSON_PARSE_ARRAY_NEXT, lex, JSON_TOKEN_ARRAY_END))
+ return;
lex->lex_level--;
@@ -610,7 +716,7 @@ parse_array(JsonLexContext *lex, JsonSemAction *sem)
/*
* Lex one token from the input stream.
*/
-static inline void
+static inline bool
json_lex(JsonLexContext *lex)
{
char *s;
@@ -720,7 +826,9 @@ json_lex(JsonLexContext *lex)
{
lex->prev_token_terminator = lex->token_terminator;
lex->token_terminator = s + 1;
- report_invalid_token(lex);
+ if (!lex_set_error(lex))
+ report_invalid_token(lex);
+ return false;
}
/*
@@ -736,16 +844,18 @@ json_lex(JsonLexContext *lex)
lex->token_type = JSON_TOKEN_TRUE;
else if (memcmp(s, "null", 4) == 0)
lex->token_type = JSON_TOKEN_NULL;
- else
+ else if (!lex_set_error(lex))
report_invalid_token(lex);
}
else if (p - s == 5 && memcmp(s, "false", 5) == 0)
lex->token_type = JSON_TOKEN_FALSE;
- else
+ else if (!lex_set_error(lex))
report_invalid_token(lex);
}
} /* end of switch */
+
+ return !lex->error;
}
/*
@@ -772,7 +882,9 @@ json_lex_string(JsonLexContext *lex)
if (len >= lex->input_length)
{
lex->token_terminator = s;
- report_invalid_token(lex);
+ if (!lex_set_error(lex))
+ report_invalid_token(lex);
+ return;
}
else if (*s == '"')
break;
@@ -781,6 +893,8 @@ json_lex_string(JsonLexContext *lex)
/* Per RFC4627, these characters MUST be escaped. */
/* Since *s isn't printable, exclude it from the context string */
lex->token_terminator = s;
+ if (lex_set_error(lex))
+ return;
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s", "json"),
@@ -796,7 +910,9 @@ json_lex_string(JsonLexContext *lex)
if (len >= lex->input_length)
{
lex->token_terminator = s;
- report_invalid_token(lex);
+ if (!lex_set_error(lex))
+ report_invalid_token(lex);
+ return;
}
else if (*s == 'u')
{
@@ -810,7 +926,9 @@ json_lex_string(JsonLexContext *lex)
if (len >= lex->input_length)
{
lex->token_terminator = s;
- report_invalid_token(lex);
+ if (!lex_set_error(lex))
+ report_invalid_token(lex);
+ return;
}
else if (*s >= '0' && *s <= '9')
ch = (ch * 16) + (*s - '0');
@@ -820,6 +938,8 @@ json_lex_string(JsonLexContext *lex)
ch = (ch * 16) + (*s - 'A') + 10;
else
{
+ if (lex_set_error(lex))
+ return;
lex->token_terminator = s + pg_mblen(s);
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
@@ -837,33 +957,45 @@ json_lex_string(JsonLexContext *lex)
if (ch >= 0xd800 && ch <= 0xdbff)
{
if (hi_surrogate != -1)
+ {
+ if (lex_set_error(lex))
+ return;
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s",
"json"),
errdetail("Unicode high surrogate must not follow a high surrogate."),
report_json_context(lex)));
+ }
hi_surrogate = (ch & 0x3ff) << 10;
continue;
}
else if (ch >= 0xdc00 && ch <= 0xdfff)
{
if (hi_surrogate == -1)
+ {
+ if (lex_set_error(lex))
+ return;
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s", "json"),
errdetail("Unicode low surrogate must follow a high surrogate."),
report_json_context(lex)));
+ }
ch = 0x10000 + hi_surrogate + (ch & 0x3ff);
hi_surrogate = -1;
}
if (hi_surrogate != -1)
+ {
+ if (lex_set_error(lex))
+ return;
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s", "json"),
errdetail("Unicode low surrogate must follow a high surrogate."),
report_json_context(lex)));
+ }
/*
* For UTF8, replace the escape sequence by the actual
@@ -875,6 +1007,8 @@ json_lex_string(JsonLexContext *lex)
if (ch == 0)
{
/* We can't allow this, since our TEXT type doesn't */
+ if (lex_set_error(lex))
+ return;
ereport(ERROR,
(errcode(ERRCODE_UNTRANSLATABLE_CHARACTER),
errmsg("unsupported Unicode escape sequence"),
@@ -898,6 +1032,8 @@ json_lex_string(JsonLexContext *lex)
}
else
{
+ if (lex_set_error(lex))
+ return;
ereport(ERROR,
(errcode(ERRCODE_UNTRANSLATABLE_CHARACTER),
errmsg("unsupported Unicode escape sequence"),
@@ -910,12 +1046,16 @@ json_lex_string(JsonLexContext *lex)
else if (lex->strval != NULL)
{
if (hi_surrogate != -1)
+ {
+ if (lex_set_error(lex))
+ return;
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s",
"json"),
errdetail("Unicode low surrogate must follow a high surrogate."),
report_json_context(lex)));
+ }
switch (*s)
{
@@ -941,6 +1081,8 @@ json_lex_string(JsonLexContext *lex)
break;
default:
/* Not a valid string escape, so error out. */
+ if (lex_set_error(lex))
+ return;
lex->token_terminator = s + pg_mblen(s);
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
@@ -960,6 +1102,8 @@ json_lex_string(JsonLexContext *lex)
* replace it with a switch statement, but testing so far has
* shown it's not a performance win.
*/
+ if (lex_set_error(lex))
+ return;
lex->token_terminator = s + pg_mblen(s);
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
@@ -973,11 +1117,15 @@ json_lex_string(JsonLexContext *lex)
else if (lex->strval != NULL)
{
if (hi_surrogate != -1)
+ {
+ if (lex_set_error(lex))
+ return;
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s", "json"),
errdetail("Unicode low surrogate must follow a high surrogate."),
report_json_context(lex)));
+ }
appendStringInfoChar(lex->strval, *s);
}
@@ -985,11 +1133,15 @@ json_lex_string(JsonLexContext *lex)
}
if (hi_surrogate != -1)
+ {
+ if (lex_set_error(lex))
+ return;
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s", "json"),
errdetail("Unicode low surrogate must follow a high surrogate."),
report_json_context(lex)));
+ }
/* Hooray, we found the end of the string! */
lex->prev_token_terminator = lex->token_terminator;
@@ -1112,7 +1264,7 @@ json_lex_number(JsonLexContext *lex, char *s,
lex->prev_token_terminator = lex->token_terminator;
lex->token_terminator = s;
/* handle error if any */
- if (error)
+ if (error && !lex_set_error(lex))
report_invalid_token(lex);
}
}
@@ -1506,7 +1658,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, DATEOID);
+ JsonEncodeDateTime(buf, val, DATEOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1514,7 +1666,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1522,7 +1674,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1550,10 +1702,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
/*
* Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'. Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
*/
char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
{
if (!buf)
buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1783,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
const char *tzn = NULL;
timestamp = DatumGetTimestampTz(value);
+
+ /*
+ * If time-zone is specified, we apply a time-zone shift,
+ * convert timestamptz to pg_tm as if it was without
+ * time-zone, and then use specified time-zone for encoding
+ * timestamp into a string.
+ */
+ if (tzp)
+ {
+ tz = *tzp;
+ timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+ }
+
/* Same as timestamptz_out(), but forcing DateStyle */
if (TIMESTAMP_NOT_FINITE(timestamp))
EncodeSpecialTimestamp(timestamp, buf);
- else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+ else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+ tzp ? NULL : &tzn, NULL) == 0)
+ {
+ if (tzp)
+ tm.tm_isdst = 1; /* set time-zone presence flag */
+
EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
@@ -1938,8 +2110,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;
@@ -1979,9 +2151,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))
{
@@ -1993,7 +2170,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))
{
@@ -2011,6 +2188,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
*/
@@ -2034,18 +2230,115 @@ json_agg_finalfn(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, "]"));
}
+static inline void
+json_unique_check_init(JsonUniqueCheckContext *cxt,
+ StringInfo result, int nkeys)
+{
+ cxt->mcxt = CurrentMemoryContext;
+ cxt->nkeys = 0;
+ cxt->nallocated = nkeys ? nkeys : 16;
+ cxt->keys = palloc(sizeof(*cxt->keys) * cxt->nallocated);
+ cxt->result = result;
+ cxt->skipped_keys.data = NULL;
+}
+
+static inline void
+json_unique_check_free(JsonUniqueCheckContext *cxt)
+{
+ if (cxt->keys)
+ pfree(cxt->keys);
+
+ if (cxt->skipped_keys.data)
+ pfree(cxt->skipped_keys.data);
+}
+
+/* On-demand initialization of skipped_keys StringInfo structure */
+static inline StringInfo
+json_unique_check_get_skipped_keys(JsonUniqueCheckContext *cxt)
+{
+ StringInfo out = &cxt->skipped_keys;
+
+ if (!out->data)
+ {
+ MemoryContext oldcxt = MemoryContextSwitchTo(cxt->mcxt);
+ initStringInfo(out);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ return out;
+}
+
+/*
+ * Save current key offset (key is not yet appended) to the key list, key
+ * length is saved later in json_unique_check_key() when the key is appended.
+ */
+static inline void
+json_unique_check_save_key_offset(JsonUniqueCheckContext *cxt, StringInfo out)
+{
+ if (cxt->nkeys >= cxt->nallocated)
+ {
+ cxt->nallocated *= 2;
+ cxt->keys = repalloc(cxt->keys, sizeof(*cxt->keys) * cxt->nallocated);
+ }
+
+ cxt->keys[cxt->nkeys++].offset = out->len;
+}
+
+/*
+ * Check uniqueness of key already appended to 'out' StringInfo.
+ */
+static inline void
+json_unique_check_key(JsonUniqueCheckContext *cxt, StringInfo out)
+{
+ struct JsonKeyInfo *keys = cxt->keys;
+ int curr = cxt->nkeys - 1;
+ int offset = keys[curr].offset;
+ int length = out->len - offset;
+ char *curr_key = &out->data[offset];
+ int i;
+
+ keys[curr].length = length; /* save current key length */
+
+ if (out == &cxt->skipped_keys)
+ /* invert offset for skipped keys for their recognition */
+ keys[curr].offset = -keys[curr].offset;
+
+ /* check collisions with previous keys */
+ for (i = 0; i < curr; i++)
+ {
+ char *prev_key;
+
+ if (cxt->keys[i].length != length)
+ continue;
+
+ offset = cxt->keys[i].offset;
+
+ prev_key = offset > 0
+ ? &cxt->result->data[offset]
+ : &cxt->skipped_keys.data[-offset];
+
+ if (!memcmp(curr_key, prev_key, length))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE),
+ errmsg("duplicate JSON key %s", curr_key)));
+ }
+}
+
/*
* 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;
if (!AggCheckCallContext(fcinfo, &aggcontext))
{
@@ -2066,6 +2359,10 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
oldcontext = MemoryContextSwitchTo(aggcontext);
state = (JsonAggState *) palloc(sizeof(JsonAggState));
state->str = makeStringInfo();
+ if (unique_keys)
+ json_unique_check_init(&state->unique_check, state->str, 0);
+ else
+ memset(&state->unique_check, 0, sizeof(state->unique_check));
MemoryContextSwitchTo(oldcontext);
arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
@@ -2093,7 +2390,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
else
{
state = (JsonAggState *) PG_GETARG_POINTER(0);
- appendStringInfoString(state->str, ", ");
}
/*
@@ -2109,11 +2405,41 @@ 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_check_get_skipped_keys(&state->unique_check);
+ }
+ else
+ {
+ out = state->str;
+
+ if (out->len > 2)
+ appendStringInfoString(out, ", ");
+ }
+
arg = PG_GETARG_DATUM(1);
- datum_to_json(arg, false, state->str, state->key_category,
+ if (unique_keys)
+ json_unique_check_save_key_offset(&state->unique_check, out);
+
+ datum_to_json(arg, false, out, state->key_category,
state->key_output_func, true);
+ if (unique_keys)
+ {
+ json_unique_check_key(&state->unique_check, out);
+
+ if (skip)
+ PG_RETURN_POINTER(state);
+ }
+
appendStringInfoString(state->str, " : ");
if (PG_ARGISNULL(2))
@@ -2127,6 +2453,26 @@ 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_objectagg aggregate function
+ */
+Datum
+json_objectagg_transfn(PG_FUNCTION_ARGS)
+{
+ return json_object_agg_transfn_worker(fcinfo,
+ PG_GETARG_BOOL(3),
+ PG_GETARG_BOOL(4));
+}
+
/*
* json_object_agg final function.
*/
@@ -2144,6 +2490,8 @@ json_object_agg_finalfn(PG_FUNCTION_ARGS)
if (state == NULL)
PG_RETURN_NULL();
+ json_unique_check_free(&state->unique_check);
+
/* Else return state with appropriate object terminator added */
PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, " }"));
}
@@ -2168,11 +2516,9 @@ catenate_stringinfo_string(StringInfo buffer, const char *addon)
return result;
}
-/*
- * SQL function json_build_object(variadic "any")
- */
-Datum
-json_build_object(PG_FUNCTION_ARGS)
+static Datum
+json_build_object_worker(FunctionCallInfo fcinfo, int first_vararg,
+ bool absent_on_null, bool unique_keys)
{
int nargs = PG_NARGS();
int i;
@@ -2181,9 +2527,11 @@ json_build_object(PG_FUNCTION_ARGS)
Datum *args;
bool *nulls;
Oid *types;
+ JsonUniqueCheckContext unique_check;
/* fetch argument values to build the object */
- nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls);
+ nargs = extract_variadic_args(fcinfo, first_vararg, false,
+ &args, &types, &nulls);
if (nargs < 0)
PG_RETURN_NULL();
@@ -2200,19 +2548,53 @@ json_build_object(PG_FUNCTION_ARGS)
appendStringInfoChar(result, '{');
+ if (unique_keys)
+ json_unique_check_init(&unique_check, result, nargs / 2);
+
for (i = 0; i < nargs; i += 2)
{
- appendStringInfoString(result, sep);
- sep = ", ";
+ StringInfo out;
+ bool skip;
+
+ /* 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_check_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", first_vararg + i + 1),
errhint("Object keys should be text.")));
- add_json(args[i], false, result, types[i], true);
+ if (unique_keys)
+ /* save key offset before key appending */
+ json_unique_check_save_key_offset(&unique_check, out);
+
+ add_json(args[i], false, out, types[i], true);
+
+ if (unique_keys)
+ {
+ /* check key uniqueness after key appending */
+ json_unique_check_key(&unique_check, out);
+
+ if (skip)
+ continue;
+ }
appendStringInfoString(result, " : ");
@@ -2222,23 +2604,43 @@ json_build_object(PG_FUNCTION_ARGS)
appendStringInfoChar(result, '}');
+ if (unique_keys)
+ json_unique_check_free(&unique_check);
+
PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
}
/*
- * degenerate case of json_build_object where it gets 0 arguments.
+ * SQL function json_build_object(variadic "any")
*/
Datum
-json_build_object_noargs(PG_FUNCTION_ARGS)
+json_build_object(PG_FUNCTION_ARGS)
{
- PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
+ return json_build_object_worker(fcinfo, 0, false, false);
}
/*
- * SQL function json_build_array(variadic "any")
+ * SQL function json_build_object_ext(absent_on_null bool, unique bool, variadic "any")
*/
Datum
-json_build_array(PG_FUNCTION_ARGS)
+json_build_object_ext(PG_FUNCTION_ARGS)
+{
+ return json_build_object_worker(fcinfo, 2,
+ PG_GETARG_BOOL(0), PG_GETARG_BOOL(1));
+}
+
+/*
+ * degenerate case of json_build_object where it gets 0 arguments.
+ */
+Datum
+json_build_object_noargs(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
+}
+
+static Datum
+json_build_array_worker(FunctionCallInfo fcinfo, int first_vararg,
+ bool absent_on_null)
{
int nargs;
int i;
@@ -2249,7 +2651,8 @@ json_build_array(PG_FUNCTION_ARGS)
Oid *types;
/* fetch argument values to build the array */
- nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls);
+ nargs = extract_variadic_args(fcinfo, first_vararg, false,
+ &args, &types, &nulls);
if (nargs < 0)
PG_RETURN_NULL();
@@ -2260,6 +2663,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);
@@ -2271,25 +2677,43 @@ json_build_array(PG_FUNCTION_ARGS)
}
/*
- * degenerate case of json_build_array where it gets 0 arguments.
+ * SQL function json_build_array(variadic "any")
*/
Datum
-json_build_array_noargs(PG_FUNCTION_ARGS)
+json_build_array(PG_FUNCTION_ARGS)
{
- PG_RETURN_TEXT_P(cstring_to_text_with_len("[]", 2));
+ return json_build_array_worker(fcinfo, 0, false);
}
/*
- * SQL function json_object(text[])
- *
- * take a one or two dimensional array of text as key/value pairs
- * for a json object.
+ * SQL function json_build_array_ext(absent_on_null bool, variadic "any")
*/
Datum
-json_object(PG_FUNCTION_ARGS)
+json_build_array_ext(PG_FUNCTION_ARGS)
{
- ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0);
- int ndims = ARR_NDIM(in_array);
+ return json_build_array_worker(fcinfo, 1, PG_GETARG_BOOL(0));
+}
+
+/*
+ * degenerate case of json_build_array where it gets 0 arguments.
+ */
+Datum
+json_build_array_noargs(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_TEXT_P(cstring_to_text_with_len("[]", 2));
+}
+
+/*
+ * SQL function json_object(text[])
+ *
+ * take a one or two dimensional array of text as key/value pairs
+ * for a json object.
+ */
+Datum
+json_object(PG_FUNCTION_ARGS)
+{
+ ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0);
+ int ndims = ARR_NDIM(in_array);
StringInfoData result;
Datum *in_datums;
bool *in_nulls;
@@ -2501,6 +2925,154 @@ escape_json(StringInfo buf, const char *str)
appendStringInfoCharMacro(buf, '"');
}
+/* Functions implementing hash table for key uniqueness check */
+static int
+json_unique_hash_match(const void *key1, const void *key2, Size keysize)
+{
+ return strcmp(*(const char **) key1, *(const char **) key2);
+}
+
+static void *
+json_unique_hash_keycopy(void *dest, const void *src, Size keysize)
+{
+ *(const char **) dest = pstrdup(*(const char **) src);
+
+ return dest;
+}
+
+static uint32
+json_unique_hash(const void *key, Size keysize)
+{
+ const char *s = *(const char **) key;
+
+ return DatumGetUInt32(hash_any((const unsigned char *) s, (int) strlen(s)));
+}
+
+/* Semantic actions for key uniqueness check */
+static void
+json_unique_object_start(void *_state)
+{
+ JsonUniqueState *state = _state;
+ JsonObjectFields *obj = palloc(sizeof(*obj));
+ HASHCTL ctl;
+
+ memset(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(char *);
+ ctl.entrysize = sizeof(char *);
+ ctl.hcxt = CurrentMemoryContext;
+ ctl.hash = json_unique_hash;
+ ctl.keycopy = json_unique_hash_keycopy;
+ ctl.match = json_unique_hash_match;
+ obj->fields = hash_create("json object hashtable",
+ 32,
+ &ctl,
+ HASH_ELEM | HASH_CONTEXT |
+ HASH_FUNCTION | HASH_COMPARE | HASH_KEYCOPY);
+ obj->parent = state->stack; /* push object to stack */
+
+ state->stack = obj;
+}
+
+static void
+json_unique_object_end(void *_state)
+{
+ JsonUniqueState *state = _state;
+
+ hash_destroy(state->stack->fields);
+
+ state->stack = state->stack->parent; /* pop object from stack */
+}
+
+static void
+json_unique_object_field_start(void *_state, char *field, bool isnull)
+{
+ JsonUniqueState *state = _state;
+ bool found;
+
+ /* find key collision in the current object */
+ (void) hash_search(state->stack->fields, &field, HASH_ENTER, &found);
+
+ if (found && !lex_set_error(state->lex))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("duplicate JSON key \"%s\"", field),
+ report_json_context(state->lex)));
+}
+
+/*
+ * json_is_valid -- check json text validity, its value type and key uniqueness
+ */
+Datum
+json_is_valid(PG_FUNCTION_ARGS)
+{
+ text *json = PG_GETARG_TEXT_P(0);
+ text *type = PG_GETARG_TEXT_P(1);
+ bool unique = PG_GETARG_BOOL(2);
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ if (!PG_ARGISNULL(1) &&
+ strncmp("any", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
+ {
+ JsonLexContext *lex;
+ JsonTokenType tok;
+
+ lex = makeJsonLexContext(json, false);
+ lex->throw_errors = false;
+
+ /* Lex exactly one token from the input and check its type. */
+ if (!json_lex(lex))
+ PG_RETURN_BOOL(false); /* invalid json */
+
+ tok = lex_peek(lex);
+
+ if (!strncmp("object", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
+ {
+ if (tok != JSON_TOKEN_OBJECT_START)
+ PG_RETURN_BOOL(false); /* json is not a object */
+ }
+ else if (!strncmp("array", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
+ {
+ if (tok != JSON_TOKEN_ARRAY_START)
+ PG_RETURN_BOOL(false); /* json is not an array */
+ }
+ else
+ {
+ if (tok == JSON_TOKEN_OBJECT_START ||
+ tok == JSON_TOKEN_ARRAY_START)
+ PG_RETURN_BOOL(false); /* json is not a scalar */
+ }
+ }
+
+ /* do full parsing pass only for uniqueness check or JSON text validation */
+ if (unique ||
+ get_fn_expr_argtype(fcinfo->flinfo, 0) != JSONOID)
+ {
+ JsonLexContext *lex = makeJsonLexContext(json, unique);
+ JsonSemAction uniqueSemAction = {0};
+ JsonUniqueState state;
+
+ if (unique)
+ {
+ state.lex = lex;
+ state.stack = NULL;
+
+ 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;
+ }
+
+ lex->throw_errors = false;
+
+ if (!pg_parse_json(lex, unique ? &uniqueSemAction : &nullSemAction))
+ PG_RETURN_BOOL(false); /* invalid json or key collision found */
+ }
+
+ PG_RETURN_BOOL(true); /* ok */
+}
+
/*
* SQL function json_typeof(json) -> text
*
@@ -2555,3 +3127,829 @@ json_typeof(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(type));
}
+
+/*
+ * Initialize a JsonContainer from a json text, its type and size.
+ * 'type' can be JB_FOBJECT, JB_FARRAY, (JB_FARRAY | JB_FSCALAR).
+ * 'size' is a number of elements/pairs in array/object, or -1 if unknown.
+ */
+static void
+jsonInitContainer(JsonContainerData *jc, char *json, int len, int type,
+ int size)
+{
+ if (size < 0 || size > JB_CMASK)
+ size = JB_CMASK; /* unknown size */
+
+ jc->data = json;
+ jc->len = len;
+ jc->header = type | size;
+}
+
+/*
+ * Initialize a JsonContainer from a text datum.
+ */
+static void
+jsonInit(JsonContainerData *jc, Datum value)
+{
+ text *json = DatumGetTextP(value);
+ JsonLexContext *lex = makeJsonLexContext(json, false);
+ JsonTokenType tok;
+ int type;
+ int size = -1;
+
+ /* Lex exactly one token from the input and check its type. */
+ json_lex(lex);
+ tok = lex_peek(lex);
+
+ switch (tok)
+ {
+ case JSON_TOKEN_OBJECT_START:
+ type = JB_FOBJECT;
+ lex_accept(lex, tok, NULL);
+ if (lex_peek(lex) == JSON_TOKEN_OBJECT_END)
+ size = 0;
+ break;
+ case JSON_TOKEN_ARRAY_START:
+ type = JB_FARRAY;
+ lex_accept(lex, tok, NULL);
+ if (lex_peek(lex) == JSON_TOKEN_ARRAY_END)
+ size = 0;
+ break;
+ case JSON_TOKEN_STRING:
+ case JSON_TOKEN_NUMBER:
+ case JSON_TOKEN_TRUE:
+ case JSON_TOKEN_FALSE:
+ case JSON_TOKEN_NULL:
+ type = JB_FARRAY | JB_FSCALAR;
+ size = 1;
+ break;
+ default:
+ elog(ERROR, "unexpected json token: %d", tok);
+ type = jbvNull;
+ break;
+ }
+
+ pfree(lex);
+
+ jsonInitContainer(jc, VARDATA(json), VARSIZE(json) - VARHDRSZ, type, size);
+}
+
+/*
+ * Wrap JSON text into a palloc()'d Json structure.
+ */
+Json *
+JsonCreate(text *json)
+{
+ Json *res = palloc0(sizeof(*res));
+
+ jsonInit((JsonContainerData *) &res->root, PointerGetDatum(json));
+
+ return res;
+}
+
+/*
+ * Fill JsonbValue from the current iterator token.
+ * Returns true if recursion into nested object or array is needed (in this case
+ * child iterator is created and put into *pit).
+ */
+static bool
+jsonFillValue(JsonIterator **pit, JsonbValue *res, bool skipNested,
+ JsontIterState nextState)
+{
+ JsonIterator *it = *pit;
+ JsonLexContext *lex = it->lex;
+ JsonTokenType tok = lex_peek(lex);
+
+ switch (tok)
+ {
+ case JSON_TOKEN_NULL:
+ res->type = jbvNull;
+ break;
+
+ case JSON_TOKEN_TRUE:
+ res->type = jbvBool;
+ res->val.boolean = true;
+ break;
+
+ case JSON_TOKEN_FALSE:
+ res->type = jbvBool;
+ res->val.boolean = false;
+ break;
+
+ case JSON_TOKEN_STRING:
+ {
+ char *token = lex_peek_value(lex);
+ res->type = jbvString;
+ res->val.string.val = token;
+ res->val.string.len = strlen(token);
+ break;
+ }
+
+ case JSON_TOKEN_NUMBER:
+ {
+ char *token = lex_peek_value(lex);
+ res->type = jbvNumeric;
+ res->val.numeric = DatumGetNumeric(DirectFunctionCall3(
+ numeric_in, CStringGetDatum(token), 0, -1));
+ break;
+ }
+
+ case JSON_TOKEN_OBJECT_START:
+ case JSON_TOKEN_ARRAY_START:
+ {
+ JsonContainerData *cont = palloc(sizeof(*cont));
+ char *token_start = lex->token_start;
+ int len;
+
+ if (skipNested)
+ {
+ /* find the end of a container for its length calculation */
+ if (tok == JSON_TOKEN_OBJECT_START)
+ parse_object(lex, &nullSemAction);
+ else
+ parse_array(lex, &nullSemAction);
+
+ len = lex->token_start - token_start;
+ }
+ else
+ len = lex->input_length - (lex->token_start - lex->input);
+
+ jsonInitContainer(cont,
+ token_start, len,
+ tok == JSON_TOKEN_OBJECT_START ?
+ JB_FOBJECT : JB_FARRAY,
+ -1);
+
+ res->type = jbvBinary;
+ res->val.binary.data = (JsonbContainer *) cont;
+ res->val.binary.len = len;
+
+ if (skipNested)
+ return false;
+
+ /* recurse into container */
+ it->state = nextState;
+ *pit = JsonIteratorInitFromLex(cont, lex, *pit);
+ return true;
+ }
+
+ default:
+ report_parse_error(JSON_PARSE_VALUE, lex);
+ }
+
+ lex_accept(lex, tok, NULL);
+
+ return false;
+}
+
+/*
+ * Free the topmost entry in the stack of JsonIterators.
+ */
+static inline JsonIterator *
+JsonIteratorFreeAndGetParent(JsonIterator *it)
+{
+ JsonIterator *parent = it->parent;
+
+ pfree(it);
+
+ return parent;
+}
+
+/*
+ * Free the entire stack of JsonIterators.
+ */
+void
+JsonIteratorFree(JsonIterator *it)
+{
+ while (it)
+ it = JsonIteratorFreeAndGetParent(it);
+}
+
+/*
+ * Get next JsonbValue while iterating through JsonContainer.
+ *
+ * For more details, see JsonbIteratorNext().
+ */
+JsonbIteratorToken
+JsonIteratorNext(JsonIterator **pit, JsonbValue *val, bool skipNested)
+{
+ JsonIterator *it;
+
+ if (*pit == NULL)
+ return WJB_DONE;
+
+recurse:
+ it = *pit;
+
+ /* parse by recursive descent */
+ switch (it->state)
+ {
+ case JTI_ARRAY_START:
+ val->type = jbvArray;
+ val->val.array.nElems = it->isScalar ? 1 : -1;
+ val->val.array.rawScalar = it->isScalar;
+ val->val.array.elems = NULL;
+ it->state = it->isScalar ? JTI_ARRAY_ELEM_SCALAR : JTI_ARRAY_ELEM;
+ return WJB_BEGIN_ARRAY;
+
+ case JTI_ARRAY_ELEM_SCALAR:
+ {
+ (void) jsonFillValue(pit, val, skipNested, JTI_ARRAY_END);
+ it->state = JTI_ARRAY_END;
+ return WJB_ELEM;
+ }
+
+ case JTI_ARRAY_END:
+ if (!it->parent && lex_peek(it->lex) != JSON_TOKEN_END)
+ report_parse_error(JSON_PARSE_END, it->lex);
+ *pit = JsonIteratorFreeAndGetParent(*pit);
+ return WJB_END_ARRAY;
+
+ case JTI_ARRAY_ELEM:
+ if (lex_accept(it->lex, JSON_TOKEN_ARRAY_END, NULL))
+ {
+ it->state = JTI_ARRAY_END;
+ goto recurse;
+ }
+
+ if (jsonFillValue(pit, val, skipNested, JTI_ARRAY_ELEM_AFTER))
+ goto recurse;
+
+ /* fall through */
+
+ case JTI_ARRAY_ELEM_AFTER:
+ if (!lex_accept(it->lex, JSON_TOKEN_COMMA, NULL))
+ {
+ if (lex_peek(it->lex) != JSON_TOKEN_ARRAY_END)
+ report_parse_error(JSON_PARSE_ARRAY_NEXT, it->lex);
+ }
+
+ if (it->state == JTI_ARRAY_ELEM_AFTER)
+ {
+ it->state = JTI_ARRAY_ELEM;
+ goto recurse;
+ }
+
+ return WJB_ELEM;
+
+ case JTI_OBJECT_START:
+ val->type = jbvObject;
+ val->val.object.nPairs = -1;
+ val->val.object.pairs = NULL;
+ val->val.object.uniquify = false;
+ it->state = JTI_OBJECT_KEY;
+ return WJB_BEGIN_OBJECT;
+
+ case JTI_OBJECT_KEY:
+ if (lex_accept(it->lex, JSON_TOKEN_OBJECT_END, NULL))
+ {
+ if (!it->parent && lex_peek(it->lex) != JSON_TOKEN_END)
+ report_parse_error(JSON_PARSE_END, it->lex);
+ *pit = JsonIteratorFreeAndGetParent(*pit);
+ return WJB_END_OBJECT;
+ }
+
+ if (lex_peek(it->lex) != JSON_TOKEN_STRING)
+ report_parse_error(JSON_PARSE_OBJECT_START, it->lex);
+
+ (void) jsonFillValue(pit, val, true, JTI_OBJECT_VALUE);
+
+ if (!lex_accept(it->lex, JSON_TOKEN_COLON, NULL))
+ report_parse_error(JSON_PARSE_OBJECT_LABEL, it->lex);
+
+ it->state = JTI_OBJECT_VALUE;
+ return WJB_KEY;
+
+ case JTI_OBJECT_VALUE:
+ if (jsonFillValue(pit, val, skipNested, JTI_OBJECT_VALUE_AFTER))
+ goto recurse;
+
+ /* fall through */
+
+ case JTI_OBJECT_VALUE_AFTER:
+ if (!lex_accept(it->lex, JSON_TOKEN_COMMA, NULL))
+ {
+ if (lex_peek(it->lex) != JSON_TOKEN_OBJECT_END)
+ report_parse_error(JSON_PARSE_OBJECT_NEXT, it->lex);
+ }
+
+ if (it->state == JTI_OBJECT_VALUE_AFTER)
+ {
+ it->state = JTI_OBJECT_KEY;
+ goto recurse;
+ }
+
+ it->state = JTI_OBJECT_KEY;
+ return WJB_VALUE;
+
+ default:
+ break;
+ }
+
+ return WJB_DONE;
+}
+
+/* Initialize JsonIterator from json lexer which onto the first token. */
+static JsonIterator *
+JsonIteratorInitFromLex(JsonContainer *jc, JsonLexContext *lex,
+ JsonIterator *parent)
+{
+ JsonIterator *it = palloc(sizeof(JsonIterator));
+ JsonTokenType tok;
+
+ it->container = jc;
+ it->parent = parent;
+ it->lex = lex;
+
+ tok = lex_peek(it->lex);
+
+ switch (tok)
+ {
+ case JSON_TOKEN_OBJECT_START:
+ it->isScalar = false;
+ it->state = JTI_OBJECT_START;
+ lex_accept(it->lex, tok, NULL);
+ break;
+ case JSON_TOKEN_ARRAY_START:
+ it->isScalar = false;
+ it->state = JTI_ARRAY_START;
+ lex_accept(it->lex, tok, NULL);
+ break;
+ case JSON_TOKEN_STRING:
+ case JSON_TOKEN_NUMBER:
+ case JSON_TOKEN_TRUE:
+ case JSON_TOKEN_FALSE:
+ case JSON_TOKEN_NULL:
+ it->isScalar = true;
+ it->state = JTI_ARRAY_START;
+ break;
+ default:
+ report_parse_error(JSON_PARSE_VALUE, it->lex);
+ }
+
+ return it;
+}
+
+/*
+ * Given a JsonContainer, expand to JsonIterator to iterate over items
+ * fully expanded to in-memory representation for manipulation.
+ *
+ * See JsonbIteratorNext() for notes on memory management.
+ */
+JsonIterator *
+JsonIteratorInit(JsonContainer *jc)
+{
+ JsonLexContext *lex = makeJsonLexContextCstringLen(jc->data, jc->len, true);
+ json_lex(lex);
+ return JsonIteratorInitFromLex(jc, lex, NULL);
+}
+
+/*
+ * Serialize a single JsonbValue into text buffer.
+ */
+static void
+JsonEncodeJsonbValue(StringInfo buf, JsonbValue *jbv)
+{
+ check_stack_depth();
+
+ switch (jbv->type)
+ {
+ case jbvNull:
+ appendBinaryStringInfo(buf, "null", 4);
+ break;
+
+ case jbvBool:
+ if (jbv->val.boolean)
+ appendBinaryStringInfo(buf, "true", 4);
+ else
+ appendBinaryStringInfo(buf, "false", 5);
+ break;
+
+ case jbvNumeric:
+ /* replace numeric NaN with string "NaN" */
+ if (numeric_is_nan(jbv->val.numeric))
+ appendBinaryStringInfo(buf, "\"NaN\"", 5);
+ else
+ {
+ Datum str = DirectFunctionCall1(numeric_out,
+ NumericGetDatum(jbv->val.numeric));
+
+ appendStringInfoString(buf, DatumGetCString(str));
+ }
+ break;
+
+ case jbvString:
+ {
+ char *str = jbv->val.string.len < 0 ? jbv->val.string.val :
+ pnstrdup(jbv->val.string.val, jbv->val.string.len);
+
+ escape_json(buf, str);
+
+ if (jbv->val.string.len >= 0)
+ pfree(str);
+
+ break;
+ }
+
+ case jbvArray:
+ {
+ int i;
+
+ if (!jbv->val.array.rawScalar)
+ appendStringInfoChar(buf, '[');
+
+ for (i = 0; i < jbv->val.array.nElems; i++)
+ {
+ if (i > 0)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ JsonEncodeJsonbValue(buf, &jbv->val.array.elems[i]);
+ }
+
+ if (!jbv->val.array.rawScalar)
+ appendStringInfoChar(buf, ']');
+
+ break;
+ }
+
+ case jbvObject:
+ {
+ int i;
+
+ appendStringInfoChar(buf, '{');
+
+ for (i = 0; i < jbv->val.object.nPairs; i++)
+ {
+ if (i > 0)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ JsonEncodeJsonbValue(buf, &jbv->val.object.pairs[i].key);
+ appendBinaryStringInfo(buf, ": ", 2);
+ JsonEncodeJsonbValue(buf, &jbv->val.object.pairs[i].value);
+ }
+
+ appendStringInfoChar(buf, '}');
+ break;
+ }
+
+ case jbvBinary:
+ {
+ JsonContainer *json = (JsonContainer *) jbv->val.binary.data;
+
+ appendBinaryStringInfo(buf, json->data, json->len);
+ break;
+ }
+
+ default:
+ elog(ERROR, "unknown jsonb value type: %d", jbv->type);
+ break;
+ }
+}
+
+/*
+ * Turn an in-memory JsonbValue into a json for on-disk storage.
+ */
+Json *
+JsonbValueToJson(JsonbValue *jbv)
+{
+ StringInfoData buf;
+ Json *json = palloc0(sizeof(*json));
+ int type;
+ int size;
+
+ if (jbv->type == jbvBinary)
+ {
+ /* simply copy the whole container and its data */
+ JsonContainer *src = (JsonContainer *) jbv->val.binary.data;
+ JsonContainerData *dst = (JsonContainerData *) &json->root;
+
+ *dst = *src;
+ dst->data = memcpy(palloc(src->len), src->data, src->len);
+
+ return json;
+ }
+
+ initStringInfo(&buf);
+
+ JsonEncodeJsonbValue(&buf, jbv);
+
+ switch (jbv->type)
+ {
+ case jbvArray:
+ type = JB_FARRAY;
+ size = jbv->val.array.nElems;
+ break;
+
+ case jbvObject:
+ type = JB_FOBJECT;
+ size = jbv->val.object.nPairs;
+ break;
+
+ default: /* scalar */
+ type = JB_FARRAY | JB_FSCALAR;
+ size = 1;
+ break;
+ }
+
+ jsonInitContainer((JsonContainerData *) &json->root,
+ buf.data, buf.len, type, size);
+
+ return json;
+}
+
+/* Context and semantic actions for JsonGetArraySize() */
+typedef struct JsonGetArraySizeState
+{
+ int level;
+ uint32 size;
+} JsonGetArraySizeState;
+
+static void
+JsonGetArraySize_array_start(void *state)
+{
+ ((JsonGetArraySizeState *) state)->level++;
+}
+
+static void
+JsonGetArraySize_array_end(void *state)
+{
+ ((JsonGetArraySizeState *) state)->level--;
+}
+
+static void
+JsonGetArraySize_array_element_start(void *state, bool isnull)
+{
+ JsonGetArraySizeState *s = state;
+ if (s->level == 1)
+ s->size++;
+}
+
+/*
+ * Calculate the size of a json array by iterating through its elements.
+ */
+uint32
+JsonGetArraySize(JsonContainer *jc)
+{
+ JsonLexContext *lex = makeJsonLexContextCstringLen(jc->data, jc->len, false);
+ JsonSemAction sem;
+ JsonGetArraySizeState state;
+
+ state.level = 0;
+ state.size = 0;
+
+ memset(&sem, 0, sizeof(sem));
+ sem.semstate = &state;
+ sem.array_start = JsonGetArraySize_array_start;
+ sem.array_end = JsonGetArraySize_array_end;
+ sem.array_element_end = JsonGetArraySize_array_element_start;
+
+ json_lex(lex);
+ parse_array(lex, &sem);
+
+ return state.size;
+}
+
+/*
+ * Find last key in a json object by name. Returns palloc()'d copy of the
+ * corresponding value, or NULL if is not found.
+ */
+JsonbValue *
+jsonFindLastKeyInObject(JsonContainer *obj, const char *keyval, int keylen,
+ JsonbValue *res)
+{
+ JsonbValue *val = NULL;
+ JsonbValue jbv;
+ JsonIterator *it;
+ JsonbIteratorToken tok;
+
+ Assert(JsonContainerIsObject(obj));
+
+ it = JsonIteratorInit(obj);
+
+ while ((tok = JsonIteratorNext(&it, &jbv, true)) != WJB_DONE)
+ {
+ if (tok == WJB_KEY &&
+ jbv.val.string.len == keylen &&
+ !memcmp(jbv.val.string.val, keyval, keylen))
+ {
+ if (!val)
+ val = res ? res : palloc(sizeof(*val));
+
+ tok = JsonIteratorNext(&it, res, true);
+ Assert(tok == WJB_VALUE);
+ }
+ }
+
+ return val;
+}
+
+/*
+ * Find scalar element in a array. Returns palloc()'d copy of value or NULL.
+ */
+static JsonbValue *
+jsonFindValueInArray(JsonContainer *array, const JsonbValue *elem,
+ JsonbValue *res)
+{
+ JsonbValue *val = res ? res : palloc(sizeof(*val));
+ JsonIterator *it;
+ JsonbIteratorToken tok;
+
+ Assert(JsonContainerIsArray(array));
+ Assert(IsAJsonbScalar(elem));
+
+ it = JsonIteratorInit(array);
+
+ while ((tok = JsonIteratorNext(&it, val, true)) != WJB_DONE)
+ {
+ if (tok == WJB_ELEM && val->type == elem->type &&
+ equalsJsonbScalarValue(val, (JsonbValue *) elem))
+ {
+ JsonIteratorFree(it);
+ return val;
+ }
+ }
+
+ if (!res)
+ pfree(val);
+ return NULL;
+}
+
+/*
+ * Find value in object (i.e. the "value" part of some key/value pair in an
+ * object), or find a matching element if we're looking through an array.
+ * The "flags" argument allows the caller to specify which container types are
+ * of interest. If we cannot find the value, return NULL. Otherwise, return
+ * palloc()'d copy of value.
+ *
+ * For more details, see findJsonbValueFromContainer().
+ */
+JsonbValue *
+findJsonValueFromContainer(JsonContainer *jc, uint32 flags, JsonbValue *key,
+ JsonbValue *val)
+{
+ Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
+
+ if (!JsonContainerSize(jc))
+ return NULL;
+
+ if ((flags & JB_FARRAY) && JsonContainerIsArray(jc))
+ return jsonFindValueInArray(jc, key, val);
+
+ if ((flags & JB_FOBJECT) && JsonContainerIsObject(jc))
+ {
+ Assert(key->type == jbvString);
+ return jsonFindLastKeyInObject(jc, key->val.string.val,
+ key->val.string.len, val);
+ }
+
+ /* Not found */
+ return NULL;
+}
+
+/*
+ * Get i-th element of a json array.
+ *
+ * Returns palloc()'d copy of the value, or NULL if it does not exist.
+ */
+JsonbValue *
+getIthJsonValueFromContainer(JsonContainer *array, uint32 index,
+ JsonbValue *res)
+{
+ JsonbValue *val = res ? res : palloc(sizeof(*val));
+ JsonIterator *it;
+ JsonbIteratorToken tok;
+
+ Assert(JsonContainerIsArray(array));
+
+ it = JsonIteratorInit(array);
+
+ while ((tok = JsonIteratorNext(&it, val, true)) != WJB_DONE)
+ {
+ if (tok == WJB_ELEM)
+ {
+ if (index-- == 0)
+ {
+ JsonIteratorFree(it);
+ return val;
+ }
+ }
+ }
+
+ if (!res)
+ pfree(val);
+
+ return NULL;
+}
+
+/*
+ * Push json JsonbValue into JsonbParseState.
+ *
+ * Used for converting an in-memory JsonbValue to a json. For more details,
+ * see pushJsonbValue(). This function differs from pushJsonbValue() only by
+ * resetting "uniquify" flag in objects.
+ */
+JsonbValue *
+pushJsonValue(JsonbParseState **pstate, JsonbIteratorToken seq,
+ JsonbValue *jbval)
+{
+ JsonIterator *it;
+ JsonbValue *res = NULL;
+ JsonbValue v;
+ JsonbIteratorToken tok;
+
+ if (!jbval || (seq != WJB_ELEM && seq != WJB_VALUE) ||
+ jbval->type != jbvBinary)
+ {
+ /* drop through */
+ res = pushJsonbValueScalar(pstate, seq, jbval);
+
+ /* reset "uniquify" flag of objects */
+ if (seq == WJB_BEGIN_OBJECT)
+ (*pstate)->contVal.val.object.uniquify = false;
+
+ return res;
+ }
+
+ /* unpack the binary and add each piece to the pstate */
+ it = JsonIteratorInit((JsonContainer *) jbval->val.binary.data);
+ while ((tok = JsonIteratorNext(&it, &v, false)) != WJB_DONE)
+ {
+ res = pushJsonbValueScalar(pstate, tok,
+ tok < WJB_BEGIN_ARRAY ? &v : NULL);
+
+ /* reset "uniquify" flag of objects */
+ if (tok == WJB_BEGIN_OBJECT)
+ (*pstate)->contVal.val.object.uniquify = false;
+ }
+
+ return res;
+}
+
+/*
+ * Extract scalar JsonbValue from a scalar json.
+ */
+bool
+JsonExtractScalar(JsonContainer *jbc, JsonbValue *res)
+{
+ JsonIterator *it = JsonIteratorInit(jbc);
+ JsonbIteratorToken tok PG_USED_FOR_ASSERTS_ONLY;
+ JsonbValue tmp;
+
+ if (!JsonContainerIsScalar(jbc))
+ return false;
+
+ tok = JsonIteratorNext(&it, &tmp, true);
+ Assert(tok == WJB_BEGIN_ARRAY);
+ Assert(tmp.val.array.nElems == 1 && tmp.val.array.rawScalar);
+
+ tok = JsonIteratorNext(&it, res, true);
+ Assert(tok == WJB_ELEM);
+ Assert(IsAJsonbScalar(res));
+
+ tok = JsonIteratorNext(&it, &tmp, true);
+ Assert(tok == WJB_END_ARRAY);
+
+ return true;
+}
+
+/*
+ * Turn a Json into its C-string representation with stripping quotes from
+ * scalar strings.
+ */
+char *
+JsonUnquote(Json *jb)
+{
+ if (JsonContainerIsScalar(&jb->root))
+ {
+ JsonbValue v;
+
+ JsonExtractScalar(&jb->root, &v);
+
+ if (v.type == jbvString)
+ return pnstrdup(v.val.string.val, v.val.string.len);
+ }
+
+ return JsonToCString(NULL, &jb->root, 0);
+}
+
+/*
+ * Turn a JsonContainer into its C-string representation.
+ */
+char *
+JsonToCString(StringInfo out, JsonContainer *jc, int estimated_len)
+{
+ if (out)
+ {
+ appendBinaryStringInfo(out, jc->data, jc->len);
+ return out->data;
+ }
+ else
+ {
+ char *str = palloc(jc->len + 1);
+
+ memcpy(str, jc->data, jc->len);
+ str[jc->len] = 0;
+
+ return str;
+ }
+}
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 69f41ab455..84db858aef 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -52,6 +52,16 @@ typedef enum /* type categories for datum_to_jsonb */
JSONBTYPE_OTHER /* all else */
} JsonbTypeCategory;
+/* Context for key uniqueness check */
+typedef struct JsonbUniqueCheckContext
+{
+ JsonbValue *obj; /* object containing skipped keys also */
+ int *skipped_keys; /* array of skipped key-value pair indices */
+ int skipped_keys_allocated;
+ int skipped_keys_count;
+ MemoryContext mcxt; /* context for saving skipped keys */
+} JsonbUniqueCheckContext;
+
typedef struct JsonbAggState
{
JsonbInState *res;
@@ -59,6 +69,7 @@ typedef struct JsonbAggState
Oid key_output_func;
JsonbTypeCategory val_category;
Oid val_output_func;
+ JsonbUniqueCheckContext unique_check;
} JsonbAggState;
static inline Datum jsonb_from_cstring(char *json, int len);
@@ -227,6 +238,23 @@ jsonb_typeof(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(result));
}
+/*
+ * jsonb_from_bytea -- bytea to jsonb conversion with validation
+ */
+Datum
+jsonb_from_bytea(PG_FUNCTION_ARGS)
+{
+ bytea *ba = PG_GETARG_BYTEA_P(0);
+ Jsonb *jb = (Jsonb *) ba;
+
+ if (!JsonbValidate(VARDATA(jb), VARSIZE(jb) - VARHDRSZ))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+ errmsg("incorrect jsonb binary data format")));
+
+ PG_RETURN_JSONB_P(jb);
+}
+
/*
* jsonb_from_cstring
*
@@ -805,17 +833,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
break;
case JSONBTYPE_DATE:
jb.type = jbvString;
- jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+ jb.val.string.val = JsonEncodeDateTime(NULL, val,
+ DATEOID, NULL);
jb.val.string.len = strlen(jb.val.string.val);
break;
case JSONBTYPE_TIMESTAMP:
jb.type = jbvString;
- jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+ jb.val.string.val = JsonEncodeDateTime(NULL, val,
+ TIMESTAMPOID, NULL);
jb.val.string.len = strlen(jb.val.string.val);
break;
case JSONBTYPE_TIMESTAMPTZ:
jb.type = jbvString;
- jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+ jb.val.string.val = JsonEncodeDateTime(NULL, val,
+ TIMESTAMPTZOID, NULL);
jb.val.string.len = strlen(jb.val.string.val);
break;
case JSONBTYPE_JSONCAST:
@@ -1132,11 +1163,121 @@ to_jsonb(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(JsonbValueToJsonb(result.res));
}
+static inline void
+jsonb_unique_check_init(JsonbUniqueCheckContext *cxt, JsonbValue *obj,
+ MemoryContext mcxt)
+{
+ cxt->mcxt = mcxt;
+ cxt->obj = obj;
+ cxt->skipped_keys = NULL;
+ cxt->skipped_keys_count = 0;
+ cxt->skipped_keys_allocated = 0;
+}
+
/*
- * SQL function jsonb_build_object(variadic "any")
+ * Save the index of the skipped key-value pair that has just been appended
+ * to the object.
*/
-Datum
-jsonb_build_object(PG_FUNCTION_ARGS)
+static inline void
+jsonb_unique_check_add_skipped(JsonbUniqueCheckContext *cxt)
+{
+ /*
+ * Make a room for the skipped index plus one additional index
+ * (see jsonb_unique_check_remove_skipped_keys()).
+ */
+ if (cxt->skipped_keys_count + 1 >= cxt->skipped_keys_allocated)
+ {
+ if (cxt->skipped_keys_allocated)
+ {
+ cxt->skipped_keys_allocated *= 2;
+ cxt->skipped_keys = repalloc(cxt->skipped_keys,
+ sizeof(*cxt->skipped_keys) *
+ cxt->skipped_keys_allocated);
+ }
+ else
+ {
+ cxt->skipped_keys_allocated = 16;
+ cxt->skipped_keys = MemoryContextAlloc(cxt->mcxt,
+ sizeof(*cxt->skipped_keys) *
+ cxt->skipped_keys_allocated);
+ }
+ }
+
+ cxt->skipped_keys[cxt->skipped_keys_count++] = cxt->obj->val.object.nPairs;
+}
+
+/*
+ * Check uniqueness of the key that has just been appended to the object.
+ */
+static inline void
+jsonb_unique_check_key(JsonbUniqueCheckContext *cxt, bool skip)
+{
+ JsonbPair *pair = cxt->obj->val.object.pairs;
+ /* nPairs is incremented only after the value is appended */
+ JsonbPair *last = &pair[cxt->obj->val.object.nPairs];
+
+ for (; pair < last; pair++)
+ if (pair->key.val.string.len ==
+ last->key.val.string.len &&
+ !memcmp(pair->key.val.string.val,
+ last->key.val.string.val,
+ last->key.val.string.len))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE),
+ errmsg("duplicate JSON key \"%*s\"",
+ last->key.val.string.len,
+ last->key.val.string.val)));
+
+ if (skip)
+ {
+ /* save skipped key index */
+ jsonb_unique_check_add_skipped(cxt);
+
+ /* add dummy null value for the skipped key */
+ last->value.type = jbvNull;
+ cxt->obj->val.object.nPairs++;
+ }
+}
+
+/*
+ * Remove skipped key-value pairs from the resulting object.
+ */
+static void
+jsonb_unique_check_remove_skipped_keys(JsonbUniqueCheckContext *cxt)
+{
+ int *skipped_keys = cxt->skipped_keys;
+ int skipped_keys_count = cxt->skipped_keys_count;
+
+ if (!skipped_keys_count)
+ return;
+
+ if (cxt->obj->val.object.nPairs > skipped_keys_count)
+ {
+ /* remove skipped key-value pairs */
+ JsonbPair *pairs = cxt->obj->val.object.pairs;
+ int i;
+
+ /* save total pair count into the last element of skipped_keys */
+ Assert(cxt->skipped_keys_count < cxt->skipped_keys_allocated);
+ cxt->skipped_keys[cxt->skipped_keys_count] = cxt->obj->val.object.nPairs;
+
+ for (i = 0; i < skipped_keys_count; i++)
+ {
+ int skipped_key = skipped_keys[i];
+ int nkeys = skipped_keys[i + 1] - skipped_key - 1;
+
+ memmove(&pairs[skipped_key - i],
+ &pairs[skipped_key + 1],
+ sizeof(JsonbPair) * nkeys);
+ }
+ }
+
+ cxt->obj->val.object.nPairs -= skipped_keys_count;
+}
+
+static Datum
+jsonb_build_object_worker(FunctionCallInfo fcinfo, int first_vararg,
+ bool absent_on_null, bool unique_keys)
{
int nargs;
int i;
@@ -1144,9 +1285,11 @@ jsonb_build_object(PG_FUNCTION_ARGS)
Datum *args;
bool *nulls;
Oid *types;
+ JsonbUniqueCheckContext unique_check;
/* build argument values to build the object */
- nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
+ nargs = extract_variadic_args(fcinfo, first_vararg, true,
+ &args, &types, &nulls);
if (nargs < 0)
PG_RETURN_NULL();
@@ -1163,25 +1306,68 @@ jsonb_build_object(PG_FUNCTION_ARGS)
result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL);
+ /* if (unique_keys) */
+ jsonb_unique_check_init(&unique_check, result.res, CurrentMemoryContext);
+
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)));
+ errmsg("argument %d: key must not be null",
+ first_vararg + 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);
+ if (unique_keys)
+ {
+ jsonb_unique_check_key(&unique_check, skip);
+
+ if (skip)
+ continue; /* do not process the value if the key is skipped */
+ }
+
/* process value */
add_jsonb(args[i + 1], nulls[i + 1], &result, types[i + 1], false);
}
+ if (unique_keys && absent_on_null)
+ jsonb_unique_check_remove_skipped_keys(&unique_check);
+
result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL);
PG_RETURN_POINTER(JsonbValueToJsonb(result.res));
}
+/*
+ * SQL function jsonb_build_object(variadic "any")
+ */
+Datum
+jsonb_build_object(PG_FUNCTION_ARGS)
+{
+ return jsonb_build_object_worker(fcinfo, 0, false, false);
+}
+
+/*
+ * SQL function jsonb_build_object_ext(absent_on_null bool, unique bool, variadic "any")
+ */
+Datum
+jsonb_build_object_ext(PG_FUNCTION_ARGS)
+{
+ return jsonb_build_object_worker(fcinfo, 2,
+ PG_GETARG_BOOL(0), PG_GETARG_BOOL(1));
+}
+
/*
* degenerate case of jsonb_build_object where it gets 0 arguments.
*/
@@ -1198,11 +1384,9 @@ 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)
+static Datum
+jsonb_build_array_worker(FunctionCallInfo fcinfo, int first_vararg,
+ bool absent_on_null)
{
int nargs;
int i;
@@ -1212,7 +1396,8 @@ jsonb_build_array(PG_FUNCTION_ARGS)
Oid *types;
/* build argument values to build the array */
- nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
+ nargs = extract_variadic_args(fcinfo, first_vararg, true,
+ &args, &types, &nulls);
if (nargs < 0)
PG_RETURN_NULL();
@@ -1222,13 +1407,36 @@ jsonb_build_array(PG_FUNCTION_ARGS)
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));
}
+/*
+ * SQL function jsonb_build_array(variadic "any")
+ */
+Datum
+jsonb_build_array(PG_FUNCTION_ARGS)
+{
+ return jsonb_build_array_worker(fcinfo, 0, false);
+}
+
+/*
+ * SQL function jsonb_build_array_ext(absent_on_null bool, variadic "any")
+ */
+Datum
+jsonb_build_array_ext(PG_FUNCTION_ARGS)
+{
+ return jsonb_build_array_worker(fcinfo, 1, PG_GETARG_BOOL(0));
+}
+
/*
* degenerate case of jsonb_build_array where it gets 0 arguments.
*/
@@ -1480,12 +1688,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;
@@ -1533,6 +1737,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);
@@ -1602,6 +1809,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)
{
@@ -1634,11 +1859,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;
@@ -1652,6 +1875,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
*jbval;
JsonbValue v;
JsonbIteratorToken type;
+ bool skip;
if (!AggCheckCallContext(fcinfo, &aggcontext))
{
@@ -1671,6 +1895,11 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
state->res = result;
result->res = pushJsonbValue(&result->parseState,
WJB_BEGIN_OBJECT, NULL);
+ if (unique_keys)
+ jsonb_unique_check_init(&state->unique_check, result->res,
+ aggcontext);
+ else
+ memset(&state->unique_check, 0, sizeof(state->unique_check));
MemoryContextSwitchTo(oldcontext);
arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
@@ -1706,6 +1935,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));
@@ -1761,6 +1999,18 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
}
result->res = pushJsonbValue(&result->parseState,
WJB_KEY, &v);
+
+ if (unique_keys)
+ {
+ jsonb_unique_check_key(&state->unique_check, skip);
+
+ if (skip)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ PG_RETURN_POINTER(state);
+ }
+ }
+
break;
case WJB_END_ARRAY:
break;
@@ -1833,6 +2083,26 @@ 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_objectagg aggregate function
+ */
+Datum
+jsonb_objectagg_transfn(PG_FUNCTION_ARGS)
+{
+ return jsonb_object_agg_transfn_worker(fcinfo,
+ PG_GETARG_BOOL(3),
+ PG_GETARG_BOOL(4));
+}
+
Datum
jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
{
@@ -1867,15 +2137,58 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
}
+/*
+ * jsonb_is_valid -- check bytea jsonb validity and its value type
+ */
+Datum
+jsonb_is_valid(PG_FUNCTION_ARGS)
+{
+ bytea *ba = PG_GETARG_BYTEA_P(0);
+ text *type = PG_GETARG_TEXT_P(1);
+ Jsonb *jb = (Jsonb *) ba;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ if (get_fn_expr_argtype(fcinfo->flinfo, 0) != JSONBOID &&
+ !JsonbValidate(VARDATA(jb), VARSIZE(jb) - VARHDRSZ))
+ PG_RETURN_BOOL(false);
+
+ if (!PG_ARGISNULL(1) &&
+ strncmp("any", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
+ {
+ if (!strncmp("object", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
+ {
+ if (!JB_ROOT_IS_OBJECT(jb))
+ PG_RETURN_BOOL(false);
+ }
+ else if (!strncmp("array", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
+ {
+ if (!JB_ROOT_IS_ARRAY(jb) || JB_ROOT_IS_SCALAR(jb))
+ PG_RETURN_BOOL(false);
+ }
+ else
+ {
+ if (!JB_ROOT_IS_ARRAY(jb) || !JB_ROOT_IS_SCALAR(jb))
+ PG_RETURN_BOOL(false);
+ }
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
/*
* Extract scalar value from raw-scalar pseudo-array jsonb.
*/
bool
JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
{
+#if 0
JsonbIterator *it;
JsonbIteratorToken tok PG_USED_FOR_ASSERTS_ONLY;
JsonbValue tmp;
+#endif
+ JsonbValue *scalar PG_USED_FOR_ASSERTS_ONLY;
if (!JsonContainerIsArray(jbc) || !JsonContainerIsScalar(jbc))
{
@@ -1884,6 +2197,10 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
return false;
}
+#if 1
+ scalar = getIthJsonbValueFromContainer(jbc, 0, res);
+ Assert(scalar);
+#else
/*
* A root scalar is stored as an array of one element, so we get the array
* and then its first (and only) member.
@@ -1903,7 +2220,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
tok = JsonbIteratorNext(&it, &tmp, true);
Assert(tok == WJB_DONE);
-
+#endif
return true;
}
@@ -2064,3 +2381,65 @@ jsonb_float8(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(retValue);
}
+
+/*
+ * Construct an empty array jsonb.
+ */
+Jsonb *
+JsonbMakeEmptyArray(void)
+{
+ JsonbValue jbv;
+
+ jbv.type = jbvArray;
+ jbv.val.array.elems = NULL;
+ jbv.val.array.nElems = 0;
+ jbv.val.array.rawScalar = false;
+
+ return JsonbValueToJsonb(&jbv);
+}
+
+/*
+ * Construct an empty object jsonb.
+ */
+Jsonb *
+JsonbMakeEmptyObject(void)
+{
+ JsonbValue jbv;
+
+ jbv.type = jbvObject;
+ jbv.val.object.pairs = NULL;
+ jbv.val.object.nPairs = 0;
+
+ return JsonbValueToJsonb(&jbv);
+}
+
+/*
+ * Convert jsonb to a C-string stripping quotes from scalar strings.
+ */
+char *
+JsonbUnquote(Jsonb *jb)
+{
+ if (JB_ROOT_IS_SCALAR(jb))
+ {
+ JsonbValue v;
+
+ JsonbExtractScalar(&jb->root, &v);
+
+ if (v.type == jbvString)
+ return pnstrdup(v.val.string.val, v.val.string.len);
+ else if (v.type == jbvBool)
+ return pstrdup(v.val.boolean ? "true" : "false");
+ else if (v.type == jbvNumeric)
+ return DatumGetCString(DirectFunctionCall1(numeric_out,
+ PointerGetDatum(v.val.numeric)));
+ else if (v.type == jbvNull)
+ return pstrdup("null");
+ else
+ {
+ elog(ERROR, "unrecognized jsonb value type %d", v.type);
+ return NULL;
+ }
+ }
+ else
+ return JsonbToCString(NULL, &jb->root, VARSIZE(jb));
+}
diff --git a/src/backend/utils/adt/jsonb_op.c b/src/backend/utils/adt/jsonb_op.c
index a64206eeb1..82c4b0b2cb 100644
--- a/src/backend/utils/adt/jsonb_op.c
+++ b/src/backend/utils/adt/jsonb_op.c
@@ -24,6 +24,7 @@ jsonb_exists(PG_FUNCTION_ARGS)
Jsonb *jb = PG_GETARG_JSONB_P(0);
text *key = PG_GETARG_TEXT_PP(1);
JsonbValue kval;
+ JsonbValue vval;
JsonbValue *v = NULL;
/*
@@ -38,7 +39,7 @@ jsonb_exists(PG_FUNCTION_ARGS)
v = findJsonbValueFromContainer(&jb->root,
JB_FOBJECT | JB_FARRAY,
- &kval);
+ &kval, &vval);
PG_RETURN_BOOL(v != NULL);
}
@@ -59,6 +60,7 @@ jsonb_exists_any(PG_FUNCTION_ARGS)
for (i = 0; i < elem_count; i++)
{
JsonbValue strVal;
+ JsonbValue valVal;
if (key_nulls[i])
continue;
@@ -69,7 +71,7 @@ jsonb_exists_any(PG_FUNCTION_ARGS)
if (findJsonbValueFromContainer(&jb->root,
JB_FOBJECT | JB_FARRAY,
- &strVal) != NULL)
+ &strVal, &valVal) != NULL)
PG_RETURN_BOOL(true);
}
@@ -92,6 +94,7 @@ jsonb_exists_all(PG_FUNCTION_ARGS)
for (i = 0; i < elem_count; i++)
{
JsonbValue strVal;
+ JsonbValue valVal;
if (key_nulls[i])
continue;
@@ -102,7 +105,7 @@ jsonb_exists_all(PG_FUNCTION_ARGS)
if (findJsonbValueFromContainer(&jb->root,
JB_FOBJECT | JB_FARRAY,
- &strVal) == NULL)
+ &strVal, &valVal) == NULL)
PG_RETURN_BOOL(false);
}
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 7969f6f584..68ee5b8f6d 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -14,9 +14,12 @@
#include "postgres.h"
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "utils/builtins.h"
+#include "utils/datetime.h"
#include "utils/hashutils.h"
+#include "utils/jsonapi.h"
#include "utils/jsonb.h"
#include "utils/memutils.h"
#include "utils/varlena.h"
@@ -34,9 +37,7 @@
#define JSONB_MAX_PAIRS (Min(MaxAllocSize / sizeof(JsonbPair), JB_CMASK))
static void fillJsonbValue(JsonbContainer *container, int index,
- char *base_addr, uint32 offset,
- JsonbValue *result);
-static bool equalsJsonbScalarValue(JsonbValue *a, JsonbValue *b);
+ char *base_addr, uint32 offset, JsonbValue *result);
static int compareJsonbScalarValue(JsonbValue *a, JsonbValue *b);
static Jsonb *convertToJsonb(JsonbValue *val);
static void convertJsonbValue(StringInfo buffer, JEntry *header, JsonbValue *val, int level);
@@ -55,12 +56,10 @@ static JsonbParseState *pushState(JsonbParseState **pstate);
static void appendKey(JsonbParseState *pstate, JsonbValue *scalarVal);
static void appendValue(JsonbParseState *pstate, JsonbValue *scalarVal);
static void appendElement(JsonbParseState *pstate, JsonbValue *scalarVal);
-static int lengthCompareJsonbStringValue(const void *a, const void *b);
static int lengthCompareJsonbPair(const void *a, const void *b, void *arg);
+static int lengthCompareJsonbString(const char *val1, int len1,
+ const char *val2, int len2);
static void uniqueifyJsonbObject(JsonbValue *object);
-static JsonbValue *pushJsonbValueScalar(JsonbParseState **pstate,
- JsonbIteratorToken seq,
- JsonbValue *scalarVal);
/*
* Turn an in-memory JsonbValue into a Jsonb for on-disk storage.
@@ -297,6 +296,95 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
return res;
}
+static bool
+jsonbFindElementInArray(JsonbContainer *container, JsonbValue *key)
+{
+ JEntry *children = container->children;
+ JsonbValue result;
+ int count = JsonContainerSize(container);
+ char *base_addr = (char *) (children + count);
+ uint32 offset = 0;
+ int i;
+
+ for (i = 0; i < count; i++)
+ {
+ fillJsonbValue(container, i, base_addr, offset, &result);
+
+ if (key->type == result.type &&
+ equalsJsonbScalarValue(key, &result))
+ return true;
+
+ JBE_ADVANCE_OFFSET(offset, children[i]);
+ }
+
+ return false;
+}
+
+JsonbValue *
+jsonbFindKeyInObject(JsonbContainer *container, const char *keyVal, int keyLen,
+ JsonbValue *res)
+{
+ JEntry *children = container->children;
+ JsonbValue *result = res;
+ int count = JsonContainerSize(container);
+ /* Since this is an object, account for *Pairs* of Jentrys */
+ char *base_addr = (char *) (children + count * 2);
+ uint32 stopLow = 0,
+ stopHigh = count;
+
+ Assert(JsonContainerIsObject(container));
+
+ /* Quick out without a palloc cycle if object is empty */
+ if (count <= 0)
+ return NULL;
+
+ if (!result)
+ result = palloc(sizeof(JsonbValue));
+
+ /* Binary search on object/pair keys *only* */
+ while (stopLow < stopHigh)
+ {
+ uint32 stopMiddle;
+ int difference;
+ const char *candidateVal;
+ int candidateLen;
+
+ stopMiddle = stopLow + (stopHigh - stopLow) / 2;
+
+ candidateVal = base_addr + getJsonbOffset(container, stopMiddle);
+ candidateLen = getJsonbLength(container, stopMiddle);
+
+ difference = lengthCompareJsonbString(candidateVal, candidateLen,
+ keyVal, keyLen);
+
+ if (difference == 0)
+ {
+ /* Found our key, return corresponding value */
+ int index = stopMiddle + count;
+
+ fillJsonbValue(container, index, base_addr,
+ getJsonbOffset(container, index),
+ result);
+
+ return result;
+ }
+ else
+ {
+ if (difference < 0)
+ stopLow = stopMiddle + 1;
+ else
+ stopHigh = stopMiddle;
+ }
+ }
+
+ /* Not found */
+ if (!res)
+ pfree(result);
+
+ return NULL;
+}
+
+
/*
* Find value in object (i.e. the "value" part of some key/value pair in an
* object), or find a matching element if we're looking through an array. Do
@@ -325,88 +413,31 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
*/
JsonbValue *
findJsonbValueFromContainer(JsonbContainer *container, uint32 flags,
- JsonbValue *key)
+ JsonbValue *key, JsonbValue *res)
{
- JEntry *children = container->children;
int count = JsonContainerSize(container);
- JsonbValue *result;
Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
- /* Quick out without a palloc cycle if object/array is empty */
+ /* Quick out if object/array is empty */
if (count <= 0)
return NULL;
- result = palloc(sizeof(JsonbValue));
-
if ((flags & JB_FARRAY) && JsonContainerIsArray(container))
{
- char *base_addr = (char *) (children + count);
- uint32 offset = 0;
- int i;
-
- for (i = 0; i < count; i++)
- {
- fillJsonbValue(container, i, base_addr, offset, result);
-
- if (key->type == result->type)
- {
- if (equalsJsonbScalarValue(key, result))
- return result;
- }
-
- JBE_ADVANCE_OFFSET(offset, children[i]);
- }
+ if (jsonbFindElementInArray(container, key))
+ return key;
}
else if ((flags & JB_FOBJECT) && JsonContainerIsObject(container))
{
- /* Since this is an object, account for *Pairs* of Jentrys */
- char *base_addr = (char *) (children + count * 2);
- uint32 stopLow = 0,
- stopHigh = count;
-
/* Object key passed by caller must be a string */
Assert(key->type == jbvString);
- /* Binary search on object/pair keys *only* */
- while (stopLow < stopHigh)
- {
- uint32 stopMiddle;
- int difference;
- JsonbValue candidate;
-
- stopMiddle = stopLow + (stopHigh - stopLow) / 2;
-
- candidate.type = jbvString;
- candidate.val.string.val =
- base_addr + getJsonbOffset(container, stopMiddle);
- candidate.val.string.len = getJsonbLength(container, stopMiddle);
-
- difference = lengthCompareJsonbStringValue(&candidate, key);
-
- if (difference == 0)
- {
- /* Found our key, return corresponding value */
- int index = stopMiddle + count;
-
- fillJsonbValue(container, index, base_addr,
- getJsonbOffset(container, index),
- result);
-
- return result;
- }
- else
- {
- if (difference < 0)
- stopLow = stopMiddle + 1;
- else
- stopHigh = stopMiddle;
- }
- }
+ return jsonbFindKeyInObject(container, key->val.string.val,
+ key->val.string.len, res);
}
/* Not found */
- pfree(result);
return NULL;
}
@@ -416,9 +447,9 @@ findJsonbValueFromContainer(JsonbContainer *container, uint32 flags,
* Returns palloc()'d copy of the value, or NULL if it does not exist.
*/
JsonbValue *
-getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i)
+getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i,
+ JsonbValue *result)
{
- JsonbValue *result;
char *base_addr;
uint32 nelements;
@@ -431,7 +462,8 @@ getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i)
if (i >= nelements)
return NULL;
- result = palloc(sizeof(JsonbValue));
+ if (!result)
+ result = palloc(sizeof(JsonbValue));
fillJsonbValue(container, i, base_addr,
getJsonbOffset(container, i),
@@ -542,7 +574,7 @@ pushJsonbValue(JsonbParseState **pstate, JsonbIteratorToken seq,
* Do the actual pushing, with only scalar or pseudo-scalar-array values
* accepted.
*/
-static JsonbValue *
+JsonbValue *
pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq,
JsonbValue *scalarVal)
{
@@ -580,6 +612,7 @@ pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq,
(*pstate)->size = 4;
(*pstate)->contVal.val.object.pairs = palloc(sizeof(JsonbPair) *
(*pstate)->size);
+ (*pstate)->contVal.val.object.uniquify = true;
break;
case WJB_KEY:
Assert(scalarVal->type == jbvString);
@@ -822,6 +855,7 @@ JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested)
/* Set v to object on first object call */
val->type = jbvObject;
val->val.object.nPairs = (*it)->nElems;
+ val->val.object.uniquify = true;
/*
* v->val.object.pairs is not actually set, because we aren't
@@ -1009,6 +1043,7 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
for (;;)
{
JsonbValue *lhsVal; /* lhsVal is from pair in lhs object */
+ JsonbValue lhsValBuf;
rcont = JsonbIteratorNext(mContained, &vcontained, false);
@@ -1021,11 +1056,13 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
return true;
Assert(rcont == WJB_KEY);
+ Assert(vcontained.type == jbvString);
/* First, find value by key... */
- lhsVal = findJsonbValueFromContainer((*val)->container,
- JB_FOBJECT,
- &vcontained);
+ lhsVal = jsonbFindKeyInObject((*val)->container,
+ vcontained.val.string.val,
+ vcontained.val.string.len,
+ &lhsValBuf);
if (!lhsVal)
return false;
@@ -1126,9 +1163,7 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
if (IsAJsonbScalar(&vcontained))
{
- if (!findJsonbValueFromContainer((*val)->container,
- JB_FARRAY,
- &vcontained))
+ if (!jsonbFindElementInArray((*val)->container, &vcontained))
return false;
}
else
@@ -1295,7 +1330,7 @@ JsonbHashScalarValueExtended(const JsonbValue *scalarVal, uint64 *hash,
/*
* Are two scalar JsonbValues of the same type a and b equal?
*/
-static bool
+bool
equalsJsonbScalarValue(JsonbValue *aScalar, JsonbValue *bScalar)
{
if (aScalar->type == bScalar->type)
@@ -1754,6 +1789,15 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
}
}
+static int
+lengthCompareJsonbString(const char *val1, int len1, const char *val2, int len2)
+{
+ if (len1 == len2)
+ return memcmp(val1, val2, len1);
+ else
+ return len1 > len2 ? 1 : -1;
+}
+
/*
* Compare two jbvString JsonbValue values, a and b.
*
@@ -1766,26 +1810,17 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
* a and b are first sorted based on their length. If a tie-breaker is
* required, only then do we consider string binary equality.
*/
-static int
+int
lengthCompareJsonbStringValue(const void *a, const void *b)
{
const JsonbValue *va = (const JsonbValue *) a;
const JsonbValue *vb = (const JsonbValue *) b;
- int res;
Assert(va->type == jbvString);
Assert(vb->type == jbvString);
- if (va->val.string.len == vb->val.string.len)
- {
- res = memcmp(va->val.string.val, vb->val.string.val, va->val.string.len);
- }
- else
- {
- res = (va->val.string.len > vb->val.string.len) ? 1 : -1;
- }
-
- return res;
+ return lengthCompareJsonbString(va->val.string.val, va->val.string.len,
+ vb->val.string.val, vb->val.string.len);
}
/*
@@ -1830,6 +1865,9 @@ uniqueifyJsonbObject(JsonbValue *object)
Assert(object->type == jbvObject);
+ if (!object->val.object.uniquify)
+ return;
+
if (object->val.object.nPairs > 1)
qsort_arg(object->val.object.pairs, object->val.object.nPairs, sizeof(JsonbPair),
lengthCompareJsonbPair, &hasNonUniq);
@@ -1854,3 +1892,133 @@ uniqueifyJsonbObject(JsonbValue *object)
object->val.object.nPairs = res + 1 - object->val.object.pairs;
}
}
+
+bool
+JsonbValidate(const void *data, uint32 size)
+{
+ const JsonbContainer *jbc = data;
+ const JEntry *entries;
+ const char *base;
+ uint32 header;
+ uint32 nentries;
+ uint32 offset;
+ uint32 i;
+ uint32 nkeys = 0;
+
+ check_stack_depth();
+
+ /* check header size */
+ if (size < offsetof(JsonbContainer, children))
+ return false;
+
+ size -= offsetof(JsonbContainer, children);
+
+ /* read header */
+ header = jbc->header;
+ entries = &jbc->children[0];
+ nentries = header & JB_CMASK;
+
+ switch (header & ~JB_CMASK)
+ {
+ case JB_FOBJECT:
+ nkeys = nentries;
+ nentries *= 2;
+ break;
+
+ case JB_FARRAY | JB_FSCALAR:
+ if (nentries != 1)
+ /* scalar pseudo-array must have one element */
+ return false;
+ break;
+
+ case JB_FARRAY:
+ break;
+
+ default:
+ /* invalid container type */
+ return false;
+ }
+
+ /* check JEntry array size */
+ if (size < sizeof(JEntry) * nentries)
+ return false;
+
+ size -= sizeof(JEntry) * nentries;
+
+ base = (const char *) &entries[nentries];
+
+ for (i = 0, offset = 0; i < nentries; i++)
+ {
+ JEntry entry = entries[i];
+ uint32 offlen = JBE_OFFLENFLD(entry);
+ uint32 length;
+ uint32 nextOffset;
+
+ if (JBE_HAS_OFF(entry))
+ {
+ nextOffset = offlen;
+ length = nextOffset - offset;
+ }
+ else
+ {
+ length = offlen;
+ nextOffset = offset + length;
+ }
+
+ if (nextOffset < offset || nextOffset > size)
+ return false;
+
+ /* check that object key is string */
+ if (i < nkeys && !JBE_ISSTRING(entry))
+ return false;
+
+ switch (entry & JENTRY_TYPEMASK)
+ {
+ case JENTRY_ISSTRING:
+ break;
+
+ case JENTRY_ISNUMERIC:
+ {
+ uint32 padding = INTALIGN(offset) - offset;
+
+ if (length < padding)
+ return false;
+
+ if (length - padding < sizeof(struct varlena))
+ return false;
+
+ /* TODO JsonValidateNumeric(); */
+ break;
+ }
+
+ case JENTRY_ISBOOL_FALSE:
+ case JENTRY_ISBOOL_TRUE:
+ case JENTRY_ISNULL:
+ if (length)
+ return false;
+
+ break;
+
+ case JENTRY_ISCONTAINER:
+ {
+ uint32 padding = INTALIGN(offset) - offset;
+
+ if (length < padding)
+ return false;
+
+ if (!JsonbValidate(&base[offset + padding],
+ length - padding))
+ return false;
+
+ break;
+ }
+
+ default:
+ return false;
+ }
+
+ offset = nextOffset;
+ }
+
+ return true;
+}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index fe351edb2b..ba0d40b996 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -454,12 +454,6 @@ static Datum populate_array(ArrayIOData *aio, const char *colname,
static Datum populate_domain(DomainIOData *io, Oid typid, const char *colname,
MemoryContext mcxt, JsValue *jsv, bool isnull);
-/* Worker that takes care of common setup for us */
-static JsonbValue *findJsonbValueFromContainerLen(JsonbContainer *container,
- uint32 flags,
- char *key,
- uint32 keylen);
-
/* functions supporting jsonb_delete, jsonb_set and jsonb_concat */
static JsonbValue *IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
JsonbParseState **state);
@@ -718,13 +712,15 @@ jsonb_object_field(PG_FUNCTION_ARGS)
Jsonb *jb = PG_GETARG_JSONB_P(0);
text *key = PG_GETARG_TEXT_PP(1);
JsonbValue *v;
+ JsonbValue vbuf;
if (!JB_ROOT_IS_OBJECT(jb))
PG_RETURN_NULL();
- v = findJsonbValueFromContainerLen(&jb->root, JB_FOBJECT,
- VARDATA_ANY(key),
- VARSIZE_ANY_EXHDR(key));
+ v = jsonbFindKeyInObject(&jb->root,
+ VARDATA_ANY(key),
+ VARSIZE_ANY_EXHDR(key),
+ &vbuf);
if (v != NULL)
PG_RETURN_JSONB_P(JsonbValueToJsonb(v));
@@ -748,53 +744,61 @@ json_object_field_text(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
+static text *
+JsonbValueAsText(JsonbValue *v)
+{
+ switch (v->type)
+ {
+ case jbvNull:
+ return NULL;
+
+ case jbvBool:
+ return cstring_to_text(v->val.boolean ? "true" : "false");
+
+ case jbvString:
+ return cstring_to_text_with_len(v->val.string.val, v->val.string.len);
+
+ case jbvNumeric:
+ {
+ Datum cstr = DirectFunctionCall1(numeric_out,
+ PointerGetDatum(v->val.numeric));
+
+ return cstring_to_text(DatumGetCString(cstr));
+ }
+
+ case jbvBinary:
+ {
+ StringInfoData jtext;
+
+ initStringInfo(&jtext);
+ (void) JsonbToCString(&jtext, v->val.binary.data, -1);
+ return cstring_to_text_with_len(jtext.data, jtext.len);
+ }
+
+ default:
+ elog(ERROR, "unrecognized jsonb type: %d", (int) v->type);
+ return NULL;
+ }
+}
+
Datum
jsonb_object_field_text(PG_FUNCTION_ARGS)
{
Jsonb *jb = PG_GETARG_JSONB_P(0);
text *key = PG_GETARG_TEXT_PP(1);
JsonbValue *v;
+ JsonbValue vbuf;
if (!JB_ROOT_IS_OBJECT(jb))
PG_RETURN_NULL();
- v = findJsonbValueFromContainerLen(&jb->root, JB_FOBJECT,
- VARDATA_ANY(key),
- VARSIZE_ANY_EXHDR(key));
+ v = jsonbFindKeyInObject(&jb->root,
+ VARDATA_ANY(key),
+ VARSIZE_ANY_EXHDR(key),
+ &vbuf);
- if (v != NULL)
- {
- text *result = NULL;
-
- switch (v->type)
- {
- case jbvNull:
- break;
- case jbvBool:
- result = cstring_to_text(v->val.boolean ? "true" : "false");
- break;
- case jbvString:
- result = cstring_to_text_with_len(v->val.string.val, v->val.string.len);
- break;
- case jbvNumeric:
- result = cstring_to_text(DatumGetCString(DirectFunctionCall1(numeric_out,
- PointerGetDatum(v->val.numeric))));
- break;
- case jbvBinary:
- {
- StringInfo jtext = makeStringInfo();
-
- (void) JsonbToCString(jtext, v->val.binary.data, -1);
- result = cstring_to_text_with_len(jtext->data, jtext->len);
- }
- break;
- default:
- elog(ERROR, "unrecognized jsonb type: %d", (int) v->type);
- }
-
- if (result)
- PG_RETURN_TEXT_P(result);
- }
+ if (v != NULL && v->type != jbvNull)
+ PG_RETURN_TEXT_P(JsonbValueAsText(v));
PG_RETURN_NULL();
}
@@ -820,6 +824,7 @@ jsonb_array_element(PG_FUNCTION_ARGS)
Jsonb *jb = PG_GETARG_JSONB_P(0);
int element = PG_GETARG_INT32(1);
JsonbValue *v;
+ JsonbValue vbuf;
if (!JB_ROOT_IS_ARRAY(jb))
PG_RETURN_NULL();
@@ -835,7 +840,7 @@ jsonb_array_element(PG_FUNCTION_ARGS)
element += nelements;
}
- v = getIthJsonbValueFromContainer(&jb->root, element);
+ v = getIthJsonbValueFromContainer(&jb->root, element, &vbuf);
if (v != NULL)
PG_RETURN_JSONB_P(JsonbValueToJsonb(v));
@@ -863,6 +868,7 @@ jsonb_array_element_text(PG_FUNCTION_ARGS)
Jsonb *jb = PG_GETARG_JSONB_P(0);
int element = PG_GETARG_INT32(1);
JsonbValue *v;
+ JsonbValue vbuf;
if (!JB_ROOT_IS_ARRAY(jb))
PG_RETURN_NULL();
@@ -878,40 +884,10 @@ jsonb_array_element_text(PG_FUNCTION_ARGS)
element += nelements;
}
- v = getIthJsonbValueFromContainer(&jb->root, element);
- if (v != NULL)
- {
- text *result = NULL;
-
- switch (v->type)
- {
- case jbvNull:
- break;
- case jbvBool:
- result = cstring_to_text(v->val.boolean ? "true" : "false");
- break;
- case jbvString:
- result = cstring_to_text_with_len(v->val.string.val, v->val.string.len);
- break;
- case jbvNumeric:
- result = cstring_to_text(DatumGetCString(DirectFunctionCall1(numeric_out,
- PointerGetDatum(v->val.numeric))));
- break;
- case jbvBinary:
- {
- StringInfo jtext = makeStringInfo();
-
- (void) JsonbToCString(jtext, v->val.binary.data, -1);
- result = cstring_to_text_with_len(jtext->data, jtext->len);
- }
- break;
- default:
- elog(ERROR, "unrecognized jsonb type: %d", (int) v->type);
- }
+ v = getIthJsonbValueFromContainer(&jb->root, element, &vbuf);
- if (result)
- PG_RETURN_TEXT_P(result);
- }
+ if (v != NULL && v->type != jbvNull)
+ PG_RETURN_TEXT_P(JsonbValueAsText(v));
PG_RETURN_NULL();
}
@@ -1389,7 +1365,6 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text)
{
Jsonb *jb = PG_GETARG_JSONB_P(0);
ArrayType *path = PG_GETARG_ARRAYTYPE_P(1);
- Jsonb *res;
Datum *pathtext;
bool *pathnulls;
int npath;
@@ -1397,7 +1372,7 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text)
bool have_object = false,
have_array = false;
JsonbValue *jbvp = NULL;
- JsonbValue tv;
+ JsonbValue jbvbuf;
JsonbContainer *container;
/*
@@ -1425,7 +1400,7 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text)
Assert(JB_ROOT_IS_ARRAY(jb) && JB_ROOT_IS_SCALAR(jb));
/* Extract the scalar value, if it is what we'll return */
if (npath <= 0)
- jbvp = getIthJsonbValueFromContainer(container, 0);
+ jbvp = getIthJsonbValueFromContainer(container, 0, &jbvbuf);
}
/*
@@ -1455,10 +1430,10 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text)
{
if (have_object)
{
- jbvp = findJsonbValueFromContainerLen(container,
- JB_FOBJECT,
- VARDATA(pathtext[i]),
- VARSIZE(pathtext[i]) - VARHDRSZ);
+ jbvp = jsonbFindKeyInObject(container,
+ VARDATA(pathtext[i]),
+ VARSIZE(pathtext[i]) - VARHDRSZ,
+ &jbvbuf);
}
else if (have_array)
{
@@ -1494,7 +1469,7 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text)
index = nelements + lindex;
}
- jbvp = getIthJsonbValueFromContainer(container, index);
+ jbvp = getIthJsonbValueFromContainer(container, index, &jbvbuf);
}
else
{
@@ -1509,41 +1484,32 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text)
if (jbvp->type == jbvBinary)
{
- JsonbIterator *it = JsonbIteratorInit((JsonbContainer *) jbvp->val.binary.data);
- JsonbIteratorToken r;
-
- r = JsonbIteratorNext(&it, &tv, true);
- container = (JsonbContainer *) jbvp->val.binary.data;
- have_object = r == WJB_BEGIN_OBJECT;
- have_array = r == WJB_BEGIN_ARRAY;
+ container = jbvp->val.binary.data;
+ have_object = JsonContainerIsObject(container);
+ have_array = JsonContainerIsArray(container);
+ Assert(!JsonContainerIsScalar(container));
}
else
{
- have_object = jbvp->type == jbvObject;
- have_array = jbvp->type == jbvArray;
+ Assert(IsAJsonbScalar(jbvp));
+ have_object = false;
+ have_array = false;
}
}
if (as_text)
{
- /* special-case outputs for string and null values */
- if (jbvp->type == jbvString)
- PG_RETURN_TEXT_P(cstring_to_text_with_len(jbvp->val.string.val,
- jbvp->val.string.len));
- if (jbvp->type == jbvNull)
- PG_RETURN_NULL();
- }
-
- res = JsonbValueToJsonb(jbvp);
+ text *res = JsonbValueAsText(jbvp);
- if (as_text)
- {
- PG_RETURN_TEXT_P(cstring_to_text(JsonbToCString(NULL,
- &res->root,
- VARSIZE(res))));
+ if (res)
+ PG_RETURN_TEXT_P(res);
+ else
+ PG_RETURN_NULL();
}
else
{
+ Jsonb *res = JsonbValueToJsonb(jbvp);
+
/* not text mode - just hand back the jsonb */
PG_RETURN_JSONB_P(res);
}
@@ -1760,22 +1726,7 @@ each_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname, bool as_text)
}
else
{
- text *sv;
-
- if (v.type == jbvString)
- {
- /* In text mode, scalar strings should be dequoted */
- sv = cstring_to_text_with_len(v.val.string.val, v.val.string.len);
- }
- else
- {
- /* Turn anything else into a json string */
- StringInfo jtext = makeStringInfo();
- Jsonb *jb = JsonbValueToJsonb(&v);
-
- (void) JsonbToCString(jtext, &jb->root, 0);
- sv = cstring_to_text_with_len(jtext->data, jtext->len);
- }
+ text *sv = JsonbValueAsText(&v);
values[1] = PointerGetDatum(sv);
}
@@ -2070,22 +2021,7 @@ elements_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname,
}
else
{
- text *sv;
-
- if (v.type == jbvString)
- {
- /* in text mode scalar strings should be dequoted */
- sv = cstring_to_text_with_len(v.val.string.val, v.val.string.len);
- }
- else
- {
- /* turn anything else into a json string */
- StringInfo jtext = makeStringInfo();
- Jsonb *jb = JsonbValueToJsonb(&v);
-
- (void) JsonbToCString(jtext, &jb->root, 0);
- sv = cstring_to_text_with_len(jtext->data, jtext->len);
- }
+ text *sv = JsonbValueAsText(&v);
values[0] = PointerGetDatum(sv);
}
@@ -3050,6 +2986,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)
{
@@ -3086,8 +3066,7 @@ JsObjectGetField(JsObject *obj, char *field, JsValue *jsv)
else
{
jsv->val.jsonb = !obj->val.jsonb_cont ? NULL :
- findJsonbValueFromContainerLen(obj->val.jsonb_cont, JB_FOBJECT,
- field, strlen(field));
+ jsonbFindKeyInObject(obj->val.jsonb_cont, field, strlen(field), NULL);
return jsv->val.jsonb != NULL;
}
@@ -3899,22 +3878,6 @@ populate_recordset_object_field_end(void *state, char *fname, bool isnull)
}
}
-/*
- * findJsonbValueFromContainer() wrapper that sets up JsonbValue key string.
- */
-static JsonbValue *
-findJsonbValueFromContainerLen(JsonbContainer *container, uint32 flags,
- char *key, uint32 keylen)
-{
- JsonbValue k;
-
- k.type = jbvString;
- k.val.string.val = key;
- k.val.string.len = keylen;
-
- return findJsonbValueFromContainer(container, flags, &k);
-}
-
/*
* Semantic actions for json_strip_nulls.
*
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index d5da155867..0bf79969ec 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -71,18 +71,29 @@
#include "utils/json.h"
#include "utils/jsonpath.h"
+typedef struct JsonPathContext
+{
+ StringInfo buf;
+ Jsonb *vars;
+ int32 id;
+} JsonPathContext;
static Datum jsonPathFromCstring(char *in, int len);
static char *jsonPathToCstring(StringInfo out, JsonPath *in,
int estimated_len);
-static int flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
- int nestingLevel, bool insideArraySubscript);
+static JsonPath *encodeJsonPath(JsonPathParseItem *item, bool lax,
+ int32 sizeEstimation, Jsonb *vars);
+static int flattenJsonPathParseItem(JsonPathContext *cxt,
+ JsonPathParseItem *item, int nestingLevel,
+ bool insideArraySubscript);
static void alignStringInfoInt(StringInfo buf);
static int32 reserveSpaceForItemPointer(StringInfo buf);
static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
bool printBracketes);
static int operationPriority(JsonPathItemType op);
+static bool replaceVariableReference(JsonPathContext *cxt, JsonPathItem *var,
+ int32 pos);
/**************************** INPUT/OUTPUT ********************************/
@@ -169,12 +180,6 @@ jsonPathFromCstring(char *in, int len)
{
JsonPathParseResult *jsonpath = parsejsonpath(in, len);
JsonPath *res;
- StringInfoData buf;
-
- initStringInfo(&buf);
- enlargeStringInfo(&buf, 4 * len /* estimation */ );
-
- appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
if (!jsonpath)
ereport(ERROR,
@@ -182,15 +187,42 @@ jsonPathFromCstring(char *in, int len)
errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
in)));
- flattenJsonPathParseItem(&buf, jsonpath->expr, 0, false);
+ res = encodeJsonPath(jsonpath->expr, jsonpath->lax,
+ 4 * len /* estimation */ , NULL);
+
+ PG_RETURN_JSONPATH_P(res);
+}
+
+static JsonPath *
+encodeJsonPath(JsonPathParseItem *item, bool lax, int32 sizeEstimation,
+ Jsonb *vars)
+{
+ JsonPath *res;
+ JsonPathContext cxt;
+ StringInfoData buf;
+
+ if (!item)
+ return NULL;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, sizeEstimation);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ cxt.buf = &buf;
+ cxt.vars = vars;
+ cxt.id = 0;
+
+ flattenJsonPathParseItem(&cxt, item, 0, false);
res = (JsonPath *) buf.data;
SET_VARSIZE(res, buf.len);
res->header = JSONPATH_VERSION;
- if (jsonpath->lax)
+ if (lax)
res->header |= JSONPATH_LAX;
+ res->ext_items_count = cxt.id;
- PG_RETURN_JSONPATH_P(res);
+ return res;
}
/*
@@ -216,48 +248,437 @@ jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
appendBinaryStringInfo(out, "strict ", 7);
jspInit(&v, in);
- printJsonPathItem(out, &v, false, true);
+ printJsonPathItem(out, &v, false, v.type != jpiSequence);
return out->data;
}
+/*****************************INPUT/OUTPUT************************************/
+
+static inline int32
+appendJsonPathItemHeader(StringInfo buf, JsonPathItemType type, char flags)
+{
+ appendStringInfoChar(buf, (char) type);
+ appendStringInfoChar(buf, (char) flags);
+
+ /*
+ * We align buffer to int32 because a series of int32 values often goes
+ * after the header, and we want to read them directly by dereferencing
+ * int32 pointer (see jspInitByBuffer()).
+ */
+ alignStringInfoInt(buf);
+
+ /*
+ * Reserve space for next item pointer. Actual value will be recorded
+ * later, after next and children items processing.
+ */
+ return reserveSpaceForItemPointer(buf);
+}
+
+static int32
+copyJsonPathItem(JsonPathContext *cxt, JsonPathItem *item, int level,
+ int32 *pLastOffset, int32 *pNextOffset)
+{
+ StringInfo buf = cxt->buf;
+ int32 pos = buf->len - JSONPATH_HDRSZ;
+ JsonPathItem next;
+ int32 offs = 0;
+ int32 argLevel = level;
+ int32 nextOffs;
+
+ check_stack_depth();
+
+ nextOffs = appendJsonPathItemHeader(buf, item->type, item->flags);
+
+ switch (item->type)
+ {
+ case jpiNull:
+ case jpiCurrent:
+ case jpiAnyArray:
+ case jpiAnyKey:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+
+ case jpiRoot:
+ if (level > 0)
+ {
+ /* replace $ with @N */
+ int32 lev = level - 1;
+
+ buf->data[pos + JSONPATH_HDRSZ] =
+ lev > 0 ? jpiCurrentN : jpiCurrent;
+
+ if (lev > 0)
+ appendBinaryStringInfo(buf, (const char *) &lev, sizeof(lev));
+ }
+ break;
+
+ case jpiCurrentN:
+ appendBinaryStringInfo(buf, (char *) &item->content.current.level,
+ sizeof(item->content.current.level));
+ break;
+
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ case jpiArgument:
+ {
+ int32 len;
+ char *data = jspGetString(item, &len);
+
+ if (item->type == jpiVariable && cxt->vars &&
+ replaceVariableReference(cxt, item, pos))
+ break;
+
+ appendBinaryStringInfo(buf, (const char *) &len, sizeof(len));
+ appendBinaryStringInfo(buf, data, len);
+ appendStringInfoChar(buf, '\0');
+ break;
+ }
+
+ case jpiNumeric:
+ {
+ Numeric num = jspGetNumeric(item);
+
+ appendBinaryStringInfo(buf, (char *) num, VARSIZE(num));
+ break;
+ }
+
+ case jpiBool:
+ appendStringInfoChar(buf, jspGetBool(item) ? 1 : 0);
+ break;
+
+ case jpiFilter:
+ if (level)
+ argLevel++;
+ /* fall through */
+ case jpiNot:
+ case jpiExists:
+ case jpiIsUnknown:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiDatetime:
+ case jpiArray:
+ {
+ JsonPathItem arg;
+ int32 argoffs;
+ int32 argpos;
+
+ argoffs = buf->len;
+ appendBinaryStringInfo(buf, (const char *) &offs, sizeof(offs));
+
+ if (!item->content.arg)
+ break;
+
+ jspGetArg(item, &arg);
+ argpos = copyJsonPathItem(cxt, &arg, argLevel, NULL, NULL);
+ *(int32 *) &buf->data[argoffs] = argpos - pos;
+ break;
+ }
+
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ {
+ JsonPathItem larg;
+ JsonPathItem rarg;
+ int32 loffs;
+ int32 roffs;
+ int32 lpos;
+ int32 rpos;
+
+ loffs = buf->len;
+ appendBinaryStringInfo(buf, (const char *) &offs, sizeof(offs));
+
+ roffs = buf->len;
+ appendBinaryStringInfo(buf, (const char *) &offs, sizeof(offs));
+
+ jspGetLeftArg(item, &larg);
+ lpos = copyJsonPathItem(cxt, &larg, argLevel, NULL, NULL);
+ *(int32 *) &buf->data[loffs] = lpos - pos;
+
+ jspGetRightArg(item, &rarg);
+ rpos = copyJsonPathItem(cxt, &rarg, argLevel, NULL, NULL);
+ *(int32 *) &buf->data[roffs] = rpos - pos;
+
+ break;
+ }
+
+ case jpiLikeRegex:
+ {
+ JsonPathItem expr;
+ int32 eoffs;
+ int32 epos;
+
+ appendBinaryStringInfo(buf,
+ (char *) &item->content.like_regex.flags,
+ sizeof(item->content.like_regex.flags));
+
+ eoffs = buf->len;
+ appendBinaryStringInfo(buf, (char *) &offs /* fake value */, sizeof(offs));
+
+ appendBinaryStringInfo(buf,
+ (char *) &item->content.like_regex.patternlen,
+ sizeof(item->content.like_regex.patternlen));
+ appendBinaryStringInfo(buf, item->content.like_regex.pattern,
+ item->content.like_regex.patternlen);
+ appendStringInfoChar(buf, '\0');
+
+ jspInitByBuffer(&expr, item->base, item->content.like_regex.expr);
+ epos = copyJsonPathItem(cxt, &expr, argLevel, NULL, NULL);
+ *(int32 *) &buf->data[eoffs] = epos - pos;
+ }
+ break;
+
+ case jpiIndexArray:
+ {
+ int32 nelems = item->content.array.nelems;
+ int32 i;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
+ offset = buf->len;
+ appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems);
+
+ for (i = 0; i < nelems; i++, offset += 2 * sizeof(int32))
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ int32 *ppos;
+ int32 frompos;
+ int32 topos;
+ bool range;
+
+ range = jspGetArraySubscript(item, &from, &to, i);
+
+ frompos = copyJsonPathItem(cxt, &from, argLevel, NULL, NULL) - pos;
+
+ if (range)
+ topos = copyJsonPathItem(cxt, &to, argLevel, NULL, NULL) - pos;
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset];
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+
+ case jpiAny:
+ appendBinaryStringInfo(buf, (char *) &item->content.anybounds.first,
+ sizeof(item->content.anybounds.first));
+ appendBinaryStringInfo(buf, (char *) &item->content.anybounds.last,
+ sizeof(item->content.anybounds.last));
+ break;
+
+ case jpiSequence:
+ {
+ int32 nelems = item->content.sequence.nelems;
+ int32 i;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
+ offset = buf->len;
+ appendStringInfoSpaces(buf, sizeof(int32) * nelems);
+
+ for (i = 0; i < nelems; i++, offset += sizeof(int32))
+ {
+ JsonPathItem el;
+ int32 elpos;
+
+ jspGetSequenceElement(item, i, &el);
+
+ elpos = copyJsonPathItem(cxt, &el, level, NULL, NULL);
+ *(int32 *) &buf->data[offset] = elpos - pos;
+ }
+ }
+ break;
+
+ case jpiObject:
+ {
+ int32 nfields = item->content.object.nfields;
+ int32 i;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields));
+ offset = buf->len;
+ appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields);
+
+ for (i = 0; i < nfields; i++, offset += 2 * sizeof(int32))
+ {
+ JsonPathItem key;
+ JsonPathItem val;
+ int32 keypos;
+ int32 valpos;
+ int32 *ppos;
+
+ jspGetObjectField(item, i, &key, &val);
+
+ keypos = copyJsonPathItem(cxt, &key, level, NULL, NULL);
+ valpos = copyJsonPathItem(cxt, &val, level, NULL, NULL);
+
+ ppos = (int32 *) &buf->data[offset];
+ ppos[0] = keypos - pos;
+ ppos[1] = valpos - pos;
+ }
+ }
+ break;
+
+ case jpiLambda:
+ {
+ JsonPathItem arg;
+ int32 nparams = item->content.lambda.nparams;
+ int offset;
+ int32 elempos;
+ int32 i;
+
+ /* assign cache id */
+ appendBinaryStringInfo(buf, (const char *) &cxt->id, sizeof(cxt->id));
+ ++cxt->id;
+
+ appendBinaryStringInfo(buf, (char *) &nparams, sizeof(nparams));
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * (nparams + 1));
+
+ for (i = 0; i < nparams; i++)
+ {
+ jspGetLambdaParam(item, i, &arg);
+ elempos = copyJsonPathItem(cxt, &arg, level, NULL, NULL);
+ *(int32 *) &buf->data[offset] = elempos - pos;
+ offset += sizeof(int32);
+ }
+
+ jspGetLambdaExpr(item, &arg);
+ elempos = copyJsonPathItem(cxt, &arg, level, NULL, NULL);
+
+ *(int32 *) &buf->data[offset] = elempos - pos;
+ offset += sizeof(int32);
+ }
+ break;
+
+ case jpiMethod:
+ case jpiFunction:
+ {
+ int32 nargs = item->content.func.nargs;
+ int offset;
+ int i;
+
+ /* assign cache id */
+ appendBinaryStringInfo(buf, (const char *) &cxt->id, sizeof(cxt->id));
+ ++cxt->id;
+
+ appendBinaryStringInfo(buf, (char *) &nargs, sizeof(nargs));
+ offset = buf->len;
+ appendStringInfoSpaces(buf, sizeof(int32) * nargs);
+
+ appendBinaryStringInfo(buf, (char *) &item->content.func.namelen,
+ sizeof(item->content.func.namelen));
+ appendBinaryStringInfo(buf, item->content.func.name,
+ item->content.func.namelen);
+ appendStringInfoChar(buf, '\0');
+
+ for (i = 0; i < nargs; i++)
+ {
+ JsonPathItem arg;
+ int32 argpos;
+
+ jspGetFunctionArg(item, i, &arg);
+
+ argpos = copyJsonPathItem(cxt, &arg, level, NULL, NULL);
+
+ *(int32 *) &buf->data[offset] = argpos - pos;
+ offset += sizeof(int32);
+ }
+ }
+ break;
+
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (jspGetNext(item, &next))
+ {
+ int32 nextPos = copyJsonPathItem(cxt, &next, level,
+ pLastOffset, pNextOffset);
+
+ *(int32 *) &buf->data[nextOffs] = nextPos - pos;
+ }
+ else if (pLastOffset)
+ {
+ *pLastOffset = pos;
+ *pNextOffset = nextOffs;
+ }
+
+ return pos;
+}
+
+static int32
+copyJsonPath(JsonPathContext *cxt, JsonPath *jp, int level, int32 *last, int32 *next)
+{
+ JsonPathItem root;
+
+ alignStringInfoInt(cxt->buf);
+
+ jspInit(&root, jp);
+
+ return copyJsonPathItem(cxt, &root, level, last, next);
+}
+
/*
* Recursive function converting given jsonpath parse item and all its
* children into a binary representation.
*/
static int
-flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+flattenJsonPathParseItem(JsonPathContext *cxt, JsonPathParseItem *item,
int nestingLevel, bool insideArraySubscript)
{
+ StringInfo buf = cxt->buf;
/* position from beginning of jsonpath data */
int32 pos = buf->len - JSONPATH_HDRSZ;
int32 chld;
int32 next;
- int argNestingLevel = 0;
+ int32 last;
+ int argNestingLevel = nestingLevel;
check_stack_depth();
CHECK_FOR_INTERRUPTS();
- appendStringInfoChar(buf, (char) (item->type));
-
- /*
- * We align buffer to int32 because a series of int32 values often goes
- * after the header, and we want to read them directly by dereferencing
- * int32 pointer (see jspInitByBuffer()).
- */
- alignStringInfoInt(buf);
-
- /*
- * Reserve space for next item pointer. Actual value will be recorded
- * later, after next and children items processing.
- */
- next = reserveSpaceForItemPointer(buf);
+ if (item->type == jpiBinary)
+ pos = copyJsonPath(cxt, item->value.binary, nestingLevel, &last, &next);
+ else
+ {
+ next = appendJsonPathItemHeader(buf, item->type, item->flags);
+ last = pos;
+ }
switch (item->type)
{
+ case jpiBinary:
+ break;
case jpiString:
case jpiVariable:
case jpiKey:
+ case jpiArgument:
appendBinaryStringInfo(buf, (char *) &item->value.string.len,
sizeof(item->value.string.len));
appendBinaryStringInfo(buf, item->value.string.val,
@@ -286,6 +707,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiDiv:
case jpiMod:
case jpiStartsWith:
+ case jpiDatetime:
{
/*
* First, reserve place for left/right arg's positions, then
@@ -296,14 +718,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
int32 right = reserveSpaceForItemPointer(buf);
chld = !item->value.args.left ? pos :
- flattenJsonPathParseItem(buf, item->value.args.left,
- nestingLevel + argNestingLevel,
+ flattenJsonPathParseItem(cxt, item->value.args.left,
+ argNestingLevel,
insideArraySubscript);
*(int32 *) (buf->data + left) = chld - pos;
chld = !item->value.args.right ? pos :
- flattenJsonPathParseItem(buf, item->value.args.right,
- nestingLevel + argNestingLevel,
+ flattenJsonPathParseItem(cxt, item->value.args.right,
+ argNestingLevel,
insideArraySubscript);
*(int32 *) (buf->data + right) = chld - pos;
}
@@ -323,7 +745,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
item->value.like_regex.patternlen);
appendStringInfoChar(buf, '\0');
- chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr,
+ chld = flattenJsonPathParseItem(cxt, item->value.like_regex.expr,
nestingLevel,
insideArraySubscript);
*(int32 *) (buf->data + offs) = chld - pos;
@@ -337,15 +759,84 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiPlus:
case jpiMinus:
case jpiExists:
+ case jpiArray:
{
int32 arg = reserveSpaceForItemPointer(buf);
- chld = flattenJsonPathParseItem(buf, item->value.arg,
- nestingLevel + argNestingLevel,
+ if (!item->value.arg)
+ break;
+
+ chld = flattenJsonPathParseItem(cxt, item->value.arg,
+ argNestingLevel,
insideArraySubscript);
*(int32 *) (buf->data + arg) = chld - pos;
}
break;
+ case jpiLambda:
+ {
+ int32 nelems = list_length(item->value.lambda.params);
+ ListCell *lc;
+ int offset;
+ int32 elempos;
+
+ /* assign cache id */
+ appendBinaryStringInfo(buf, (const char *) &cxt->id, sizeof(cxt->id));
+ ++cxt->id;
+
+ appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * (nelems + 1));
+
+ foreach(lc, item->value.lambda.params)
+ {
+ elempos = flattenJsonPathParseItem(cxt, lfirst(lc),
+ nestingLevel,
+ insideArraySubscript);
+ *(int32 *) &buf->data[offset] = elempos - pos;
+ offset += sizeof(int32);
+ }
+
+ elempos = flattenJsonPathParseItem(cxt, item->value.lambda.expr,
+ nestingLevel,
+ insideArraySubscript);
+ *(int32 *) &buf->data[offset] = elempos - pos;
+ offset += sizeof(int32);
+ }
+ break;
+ case jpiMethod:
+ case jpiFunction:
+ {
+ int32 nargs = list_length(item->value.func.args);
+ ListCell *lc;
+ int offset;
+
+ /* assign cache id */
+ appendBinaryStringInfo(buf, (const char *) &cxt->id, sizeof(cxt->id));
+ ++cxt->id;
+
+ appendBinaryStringInfo(buf, (char *) &nargs, sizeof(nargs));
+ offset = buf->len;
+ appendStringInfoSpaces(buf, sizeof(int32) * nargs);
+
+ appendBinaryStringInfo(buf, (char *) &item->value.func.namelen,
+ sizeof(item->value.func.namelen));
+ appendBinaryStringInfo(buf, item->value.func.name,
+ item->value.func.namelen);
+ appendStringInfoChar(buf, '\0');
+
+ foreach(lc, item->value.func.args)
+ {
+ int32 argpos =
+ flattenJsonPathParseItem(cxt, lfirst(lc),
+ nestingLevel + 1,
+ insideArraySubscript);
+
+ *(int32 *) &buf->data[offset] = argpos - pos;
+ offset += sizeof(int32);
+ }
+ }
+ break;
case jpiNull:
break;
case jpiRoot:
@@ -353,6 +844,16 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiAnyArray:
case jpiAnyKey:
break;
+ case jpiCurrentN:
+ if (item->value.current.level < 0 ||
+ item->value.current.level >= nestingLevel)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid outer item reference in jsonpath @")));
+
+ appendBinaryStringInfo(buf, (char *) &item->value.current.level,
+ sizeof(item->value.current.level));
+ break;
case jpiCurrent:
if (nestingLevel <= 0)
ereport(ERROR,
@@ -382,12 +883,12 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
int32 *ppos;
int32 topos;
int32 frompos =
- flattenJsonPathParseItem(buf,
- item->value.array.elems[i].from,
- nestingLevel, true) - pos;
+ flattenJsonPathParseItem(cxt,
+ item->value.array.elems[i].from,
+ nestingLevel, true) - pos;
if (item->value.array.elems[i].to)
- topos = flattenJsonPathParseItem(buf,
+ topos = flattenJsonPathParseItem(cxt,
item->value.array.elems[i].to,
nestingLevel, true) - pos;
else
@@ -416,14 +917,69 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiDouble:
case jpiKeyValue:
break;
+ case jpiSequence:
+ {
+ int32 nelems = list_length(item->value.sequence.elems);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * nelems);
+
+ foreach(lc, item->value.sequence.elems)
+ {
+ int32 elempos =
+ flattenJsonPathParseItem(cxt, lfirst(lc), nestingLevel,
+ insideArraySubscript);
+
+ *(int32 *) &buf->data[offset] = elempos - pos;
+ offset += sizeof(int32);
+ }
+ }
+ break;
+ case jpiObject:
+ {
+ int32 nfields = list_length(item->value.object.fields);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields);
+
+ foreach(lc, item->value.object.fields)
+ {
+ JsonPathParseItem *field = lfirst(lc);
+ int32 keypos =
+ flattenJsonPathParseItem(cxt, field->value.args.left,
+ nestingLevel,
+ insideArraySubscript);
+ int32 valpos =
+ flattenJsonPathParseItem(cxt, field->value.args.right,
+ nestingLevel,
+ insideArraySubscript);
+ int32 *ppos = (int32 *) &buf->data[offset];
+
+ ppos[0] = keypos - pos;
+ ppos[1] = valpos - pos;
+
+ offset += 2 * sizeof(int32);
+ }
+ }
+ break;
default:
elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
}
if (item->next)
{
- chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
- insideArraySubscript) - pos;
+ chld = flattenJsonPathParseItem(cxt, item->next, nestingLevel,
+ insideArraySubscript) - last;
*(int32 *) (buf->data + next) = chld;
}
@@ -497,6 +1053,9 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
appendStringInfoChar(buf, '$');
escape_json(buf, jspGetString(v, NULL));
break;
+ case jpiArgument:
+ appendStringInfoString(buf, jspGetString(v, NULL));
+ break;
case jpiNumeric:
appendStringInfoString(buf,
DatumGetCString(DirectFunctionCall1(numeric_out,
@@ -563,6 +1122,8 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
appendStringInfoChar(buf, 'm');
if (v->content.like_regex.flags & JSP_REGEX_WSPACE)
appendStringInfoChar(buf, 'x');
+ if (v->content.like_regex.flags & JSP_REGEX_QUOTE)
+ appendStringInfoChar(buf, 'q');
appendStringInfoChar(buf, '"');
}
@@ -610,6 +1171,10 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
Assert(!inKey);
appendStringInfoChar(buf, '@');
break;
+ case jpiCurrentN:
+ Assert(!inKey);
+ appendStringInfo(buf, "@%d", v->content.current.level);
+ break;
case jpiRoot:
Assert(!inKey);
appendStringInfoChar(buf, '$');
@@ -636,12 +1201,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
if (i)
appendStringInfoChar(buf, ',');
- printJsonPathItem(buf, &from, false, false);
+ printJsonPathItem(buf, &from, false, from.type == jpiSequence);
if (range)
{
appendBinaryStringInfo(buf, " to ", 4);
- printJsonPathItem(buf, &to, false, false);
+ printJsonPathItem(buf, &to, false, to.type == jpiSequence);
}
}
appendStringInfoChar(buf, ']');
@@ -687,11 +1252,125 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
case jpiCeiling:
appendBinaryStringInfo(buf, ".ceiling()", 10);
break;
- case jpiDouble:
- appendBinaryStringInfo(buf, ".double()", 9);
+ case jpiDouble:
+ appendBinaryStringInfo(buf, ".double()", 9);
+ break;
+ case jpiDatetime:
+ appendBinaryStringInfo(buf, ".datetime(", 10);
+ if (v->content.args.left)
+ {
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (v->content.args.right)
+ {
+ appendBinaryStringInfo(buf, ", ", 2);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ break;
+ case jpiSequence:
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, '(');
+
+ for (i = 0; i < v->content.sequence.nelems; i++)
+ {
+ JsonPathItem elem;
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ jspGetSequenceElement(v, i, &elem);
+
+ printJsonPathItem(buf, &elem, false, elem.type == jpiSequence);
+ }
+
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiArray:
+ appendStringInfoChar(buf, '[');
+ if (v->content.arg)
+ {
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiObject:
+ appendStringInfoChar(buf, '{');
+
+ for (i = 0; i < v->content.object.nfields; i++)
+ {
+ JsonPathItem key;
+ JsonPathItem val;
+
+ jspGetObjectField(v, i, &key, &val);
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ printJsonPathItem(buf, &key, false, false);
+ appendBinaryStringInfo(buf, ": ", 2);
+ printJsonPathItem(buf, &val, false, val.type == jpiSequence);
+ }
+
+ appendStringInfoChar(buf, '}');
+ break;
+ case jpiLambda:
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, '(');
+
+ appendStringInfoChar(buf, '(');
+
+ for (i = 0; i < v->content.lambda.nparams; i++)
+ {
+ JsonPathItem elem;
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ jspGetLambdaParam(v, i, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+
+ appendStringInfoString(buf, ") => ");
+
+ jspGetLambdaExpr(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, ')');
break;
- case jpiKeyValue:
- appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ case jpiMethod:
+ case jpiFunction:
+ if (v->type == jpiMethod)
+ {
+ jspGetMethodItem(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ appendStringInfoChar(buf, '.');
+ }
+
+ escape_json(buf, v->content.func.name);
+ appendStringInfoChar(buf, '(');
+
+ for (i = v->type == jpiMethod ? 1 : 0; i < v->content.func.nargs; i++)
+ {
+ if (i > (v->type == jpiMethod ? 1 : 0))
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ jspGetFunctionArg(v, i, &elem);
+ printJsonPathItem(buf, &elem, false, elem.type == jpiSequence);
+ }
+
+ appendStringInfoChar(buf, ')');
break;
default:
elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
@@ -752,6 +1431,8 @@ jspOperationName(JsonPathItemType type)
return "floor";
case jpiCeiling:
return "ceiling";
+ case jpiDatetime:
+ return "datetime";
default:
elog(ERROR, "unrecognized jsonpath item type: %d", type);
return NULL;
@@ -763,6 +1444,8 @@ operationPriority(JsonPathItemType op)
{
switch (op)
{
+ case jpiSequence:
+ return -1;
case jpiOr:
return 0;
case jpiAnd:
@@ -782,11 +1465,13 @@ operationPriority(JsonPathItemType op)
case jpiDiv:
case jpiMod:
return 4;
+ case jpiMethod:
+ return 5;
case jpiPlus:
case jpiMinus:
- return 5;
- default:
return 6;
+ default:
+ return 7;
}
}
@@ -830,6 +1515,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
v->base = base + pos;
read_byte(v->type, base, pos);
+ read_byte(v->flags, base, pos);
pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base;
read_int32(v->nextPos, base, pos);
@@ -849,9 +1535,13 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
case jpiKeyValue:
case jpiLast:
break;
+ case jpiCurrentN:
+ read_int32(v->content.current.level, base, pos);
+ break;
case jpiKey:
case jpiString:
case jpiVariable:
+ case jpiArgument:
read_int32(v->content.value.datalen, base, pos);
/* FALLTHROUGH */
case jpiNumeric:
@@ -872,6 +1562,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
case jpiLessOrEqual:
case jpiGreaterOrEqual:
case jpiStartsWith:
+ case jpiDatetime:
read_int32(v->content.args.left, base, pos);
read_int32(v->content.args.right, base, pos);
break;
@@ -881,12 +1572,29 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
read_int32(v->content.like_regex.patternlen, base, pos);
v->content.like_regex.pattern = base + pos;
break;
+ case jpiLambda:
+ read_int32(v->content.lambda.id, base, pos);
+ read_int32(v->content.lambda.nparams, base, pos);
+ read_int32_n(v->content.lambda.params, base, pos,
+ v->content.lambda.nparams);
+ read_int32(v->content.lambda.expr, base, pos);
+ break;
+ case jpiMethod:
+ case jpiFunction:
+ read_int32(v->content.func.id, base, pos);
+ read_int32(v->content.func.nargs, base, pos);
+ read_int32_n(v->content.func.args, base, pos,
+ v->content.func.nargs);
+ read_int32(v->content.func.namelen, base, pos);
+ v->content.func.name = base + pos;
+ break;
case jpiNot:
case jpiExists:
case jpiIsUnknown:
case jpiPlus:
case jpiMinus:
case jpiFilter:
+ case jpiArray:
read_int32(v->content.arg, base, pos);
break;
case jpiIndexArray:
@@ -898,6 +1606,16 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
read_int32(v->content.anybounds.first, base, pos);
read_int32(v->content.anybounds.last, base, pos);
break;
+ case jpiSequence:
+ read_int32(v->content.sequence.nelems, base, pos);
+ read_int32_n(v->content.sequence.elems, base, pos,
+ v->content.sequence.nelems);
+ break;
+ case jpiObject:
+ read_int32(v->content.object.nfields, base, pos);
+ read_int32_n(v->content.object.fields, base, pos,
+ v->content.object.nfields * 2);
+ break;
default:
elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
}
@@ -911,7 +1629,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
v->type == jpiIsUnknown ||
v->type == jpiExists ||
v->type == jpiPlus ||
- v->type == jpiMinus);
+ v->type == jpiMinus ||
+ v->type == jpiArray);
jspInitByBuffer(a, v->base, v->content.arg);
}
@@ -932,6 +1651,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
v->type == jpiIndexArray ||
v->type == jpiFilter ||
v->type == jpiCurrent ||
+ v->type == jpiCurrentN ||
v->type == jpiExists ||
v->type == jpiRoot ||
v->type == jpiVariable ||
@@ -959,8 +1679,16 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
v->type == jpiFloor ||
v->type == jpiCeiling ||
v->type == jpiDouble ||
+ v->type == jpiDatetime ||
v->type == jpiKeyValue ||
- v->type == jpiStartsWith);
+ v->type == jpiStartsWith ||
+ v->type == jpiSequence ||
+ v->type == jpiArray ||
+ v->type == jpiObject ||
+ v->type == jpiLambda ||
+ v->type == jpiArgument ||
+ v->type == jpiFunction ||
+ v->type == jpiMethod);
if (a)
jspInitByBuffer(a, v->base, v->nextPos);
@@ -986,6 +1714,7 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
v->type == jpiMul ||
v->type == jpiDiv ||
v->type == jpiMod ||
+ v->type == jpiDatetime ||
v->type == jpiStartsWith);
jspInitByBuffer(a, v->base, v->content.args.left);
@@ -1007,6 +1736,7 @@ jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
v->type == jpiMul ||
v->type == jpiDiv ||
v->type == jpiMod ||
+ v->type == jpiDatetime ||
v->type == jpiStartsWith);
jspInitByBuffer(a, v->base, v->content.args.right);
@@ -1033,7 +1763,8 @@ jspGetString(JsonPathItem *v, int32 *len)
{
Assert(v->type == jpiKey ||
v->type == jpiString ||
- v->type == jpiVariable);
+ v->type == jpiVariable ||
+ v->type == jpiArgument);
if (len)
*len = v->content.value.datalen;
@@ -1055,3 +1786,582 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
return true;
}
+
+void
+jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem)
+{
+ Assert(v->type == jpiSequence);
+
+ jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]);
+}
+
+void
+jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val)
+{
+ Assert(v->type == jpiObject);
+ jspInitByBuffer(key, v->base, v->content.object.fields[i].key);
+ jspInitByBuffer(val, v->base, v->content.object.fields[i].val);
+}
+
+JsonPathItem *
+jspGetLambdaParam(JsonPathItem *lambda, int index, JsonPathItem *arg)
+{
+ Assert(lambda->type == jpiLambda);
+ Assert(index < lambda->content.lambda.nparams);
+
+ jspInitByBuffer(arg, lambda->base, lambda->content.lambda.params[index]);
+
+ return arg;
+}
+
+JsonPathItem *
+jspGetLambdaExpr(JsonPathItem *lambda, JsonPathItem *expr)
+{
+ Assert(lambda->type == jpiLambda);
+
+ jspInitByBuffer(expr, lambda->base, lambda->content.lambda.expr);
+
+ return expr;
+}
+
+JsonPathItem *
+jspGetFunctionArg(JsonPathItem *func, int index, JsonPathItem *arg)
+{
+ Assert(func->type == jpiMethod || func->type == jpiFunction);
+ Assert(index < func->content.func.nargs);
+
+ jspInitByBuffer(arg, func->base, func->content.func.args[index]);
+
+ return arg;
+}
+
+JsonPathItem *
+jspGetMethodItem(JsonPathItem *method, JsonPathItem *arg)
+{
+ Assert(method->type == jpiMethod);
+ return jspGetFunctionArg(method, 0, arg);
+}
+
+static void
+checkJsonPathArgsMismatch(JsonPath *jp1, JsonPath *jp2)
+{
+ if ((jp1->header & ~JSONPATH_LAX) != JSONPATH_VERSION ||
+ jp1->header != jp2->header)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("jsonpath headers does not match")));
+}
+
+static inline JsonPathParseItem *
+jspInitParseItem(JsonPathParseItem *item, JsonPathItemType type,
+ JsonPathParseItem *next)
+{
+ if (!item)
+ item = palloc(sizeof(*item));
+
+ item->type = type;
+ item->flags = 0;
+ item->next = next;
+
+ return item;
+}
+
+static inline void
+jspInitParseItemUnary(JsonPathParseItem *item, JsonPathItemType type,
+ JsonPathParseItem *next, JsonPathParseItem *arg)
+{
+ item = jspInitParseItem(item, type, next);
+ item->value.arg = arg;
+}
+
+static inline void
+jspInitParseItemBinary(JsonPathParseItem *item, JsonPathItemType type,
+ JsonPathParseItem *left, JsonPathParseItem *right,
+ JsonPathParseItem *next)
+{
+ item = jspInitParseItem(item, type, next);
+ item->value.args.left = left;
+ item->value.args.right = right;
+}
+
+static inline void
+jspInitParseItemBin(JsonPathParseItem *item, JsonPath *path,
+ JsonPathParseItem *next)
+{
+ item = jspInitParseItem(item, jpiBinary, next);
+ item->value.binary = path;
+}
+
+static inline void
+jspInitParseItemString(JsonPathParseItem *item, JsonPathItemType type,
+ char *str, uint32 len, JsonPathParseItem *next)
+{
+ item = jspInitParseItem(item, type, next);
+ item->value.string.val = str;
+ item->value.string.len = len;
+}
+
+static JsonPathParseItem *
+jspInitParseItemJsonbScalar(JsonPathParseItem *item, JsonbValue *jbv)
+{
+ /* jbv and jpi scalar types have the same values */
+ item = jspInitParseItem(item, (JsonPathItemType) jbv->type, NULL);
+
+ switch (jbv->type)
+ {
+ case jbvNull:
+ break;
+
+ case jbvBool:
+ item->value.boolean = jbv->val.boolean;
+ break;
+
+ case jbvString:
+ item->value.string.val = jbv->val.string.val;
+ item->value.string.len = jbv->val.string.len;
+ break;
+
+ case jbvNumeric:
+ item->value.numeric = jbv->val.numeric;
+ break;
+
+ default:
+ elog(ERROR, "invalid scalar jsonb value type: %d", jbv->type);
+ break;
+ }
+
+ return item;
+}
+
+static JsonPathParseItem *
+jspInitParseItemJsonb(JsonPathParseItem *item, Jsonb *jb)
+{
+ JsonbValue jbv;
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ {
+ JsonbExtractScalar(&jb->root, &jbv);
+
+ return jspInitParseItemJsonbScalar(item, &jbv);
+ }
+ else
+ {
+ JsonbIterator *it;
+ JsonbIteratorToken tok;
+ JsonPathParseItem *res = NULL;
+ JsonPathParseItem *stack = NULL;
+
+ it = JsonbIteratorInit(&jb->root);
+
+ while ((tok = JsonbIteratorNext(&it, &jbv, false)) != WJB_DONE)
+ {
+ switch (tok)
+ {
+ case WJB_BEGIN_OBJECT:
+ /* push object */
+ stack = jspInitParseItem(NULL, jpiObject, stack);
+ stack->value.object.fields = NIL;
+ break;
+
+ case WJB_BEGIN_ARRAY:
+ /* push array */
+ stack = jspInitParseItem(NULL, jpiArray, stack);
+ stack->value.arg = jspInitParseItem(NULL, jpiSequence, NULL);
+ stack->value.arg->value.sequence.elems = NIL;
+ break;
+
+ case WJB_END_OBJECT:
+ case WJB_END_ARRAY:
+ /* save and pop current container */
+ res = stack;
+ stack = stack->next;
+ res->next = NULL;
+
+ if (stack)
+ {
+ if (stack->type == jpiArray)
+ {
+ /* add container to the list of array elements */
+ stack->value.arg->value.sequence.elems =
+ lappend(stack->value.arg->value.sequence.elems,
+ res);
+ }
+ else if (stack->type == jpiObjectField)
+ {
+ /* save result into the object field value */
+ stack->value.args.right = res;
+
+ /* pop current object field */
+ res = stack;
+ stack = stack->next;
+ res->next = NULL;
+ Assert(stack && stack->type == jpiObject);
+ }
+ }
+ break;
+
+ case WJB_KEY:
+ {
+ JsonPathParseItem *key = palloc0(sizeof(*key));
+ JsonPathParseItem *field = palloc0(sizeof(*field));
+
+ Assert(stack->type == jpiObject);
+
+ jspInitParseItem(field, jpiObjectField, stack);
+ field->value.args.left = key;
+ field->value.args.right = NULL;
+
+ jspInitParseItemJsonbScalar(key, &jbv);
+
+ stack->value.object.fields =
+ lappend(stack->value.object.fields, field);
+
+ /* push current object field */
+ stack = field;
+ break;
+ }
+
+ case WJB_VALUE:
+ Assert(stack->type == jpiObjectField);
+ stack->value.args.right =
+ jspInitParseItemJsonbScalar(NULL, &jbv);
+
+ /* pop current object field */
+ res = stack;
+ stack = stack->next;
+ res->next = NULL;
+ Assert(stack && stack->type == jpiObject);
+ break;
+
+ case WJB_ELEM:
+ Assert(stack->type == jpiArray);
+ stack->value.arg->value.sequence.elems =
+ lappend(stack->value.arg->value.sequence.elems,
+ jspInitParseItemJsonbScalar(NULL, &jbv));
+ break;
+
+ default:
+ elog(ERROR, "unexpected jsonb iterator token: %d", tok);
+ }
+ }
+
+ return res;
+ }
+}
+
+/* Subroutine for implementation of operators jsonpath OP jsonpath */
+static Datum
+jsonpath_op_jsonpath(FunctionCallInfo fcinfo, JsonPathItemType op)
+{
+ JsonPath *jp1 = PG_GETARG_JSONPATH_P(0);
+ JsonPath *jp2 = PG_GETARG_JSONPATH_P(1);
+ JsonPathParseItem jpi1;
+ JsonPathParseItem jpi2;
+ JsonPathParseItem jpi;
+
+ checkJsonPathArgsMismatch(jp1, jp2);
+
+ jspInitParseItemBin(&jpi1, jp1, NULL);
+ jspInitParseItemBin(&jpi2, jp2, NULL);
+ jspInitParseItemBinary(&jpi, op, &jpi1, &jpi2, NULL);
+
+ PG_RETURN_JSONPATH_P(encodeJsonPath(&jpi,
+ (jp1->header & JSONPATH_LAX) != 0,
+ VARSIZE(jp1) + VARSIZE(jp2) -
+ JSONPATH_HDRSZ + 16, NULL));
+}
+
+/* Subroutine for implementation of operators jsonpath OP jsonb */
+static Datum
+jsonpath_op_jsonb(FunctionCallInfo fcinfo, JsonPathItemType op)
+{
+ JsonPath *jp = PG_GETARG_JSONPATH_P(0);
+ Jsonb *jb = PG_GETARG_JSONB_P(1);
+ JsonPathParseItem jpi1;
+ JsonPathParseItem *jpi2;
+ JsonPathParseItem jpi;
+
+ jspInitParseItemBin(&jpi1, jp, NULL);
+ jpi2 = jspInitParseItemJsonb(NULL, jb);
+ jspInitParseItemBinary(&jpi, op, &jpi1, jpi2, NULL);
+
+ PG_RETURN_JSONPATH_P(encodeJsonPath(&jpi,
+ (jp->header & JSONPATH_LAX) != 0,
+ VARSIZE(jp) + VARSIZE(jb), NULL));
+}
+
+/* Implementation of operator jsonpath == jsonpath */
+Datum
+jsonpath_eq_jsonpath(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonpath(fcinfo, jpiEqual);
+}
+
+/* Implementation of operator jsonpath == jsonb */
+Datum
+jsonpath_eq_jsonb(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonb(fcinfo, jpiEqual);
+}
+
+/* Implementation of operator jsonpath != jsonpath */
+Datum
+jsonpath_ne_jsonpath(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonpath(fcinfo, jpiNotEqual);
+}
+
+/* Implementation of operator jsonpath != jsonb */
+Datum
+jsonpath_ne_jsonb(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonb(fcinfo, jpiNotEqual);
+}
+
+/* Implementation of operator jsonpath < jsonpath */
+Datum
+jsonpath_lt_jsonpath(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonpath(fcinfo, jpiLess);
+}
+
+/* Implementation of operator jsonpath < jsonb */
+Datum
+jsonpath_lt_jsonb(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonb(fcinfo, jpiLess);
+}
+
+/* Implementation of operator jsonpath <= jsonpath */
+Datum
+jsonpath_le_jsonpath(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonpath(fcinfo, jpiLessOrEqual);
+}
+
+/* Implementation of operator jsonpath <= jsonb */
+Datum
+jsonpath_le_jsonb(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonb(fcinfo, jpiLessOrEqual);
+}
+
+/* Implementation of operator jsonpath > jsonpath */
+Datum
+jsonpath_gt_jsonpath(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonpath(fcinfo, jpiGreater);
+}
+
+/* Implementation of operator jsonpath > jsonb */
+Datum
+jsonpath_gt_jsonb(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonb(fcinfo, jpiGreater);
+}
+
+/* Implementation of operator jsonpath >= jsonpath */
+Datum
+jsonpath_ge_jsonpath(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonpath(fcinfo, jpiGreaterOrEqual);
+}
+
+/* Implementation of operator jsonpath >= jsonb */
+Datum
+jsonpath_ge_jsonb(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonb(fcinfo, jpiGreaterOrEqual);
+}
+
+/* Implementation of operator jsonpath + jsonpath */
+Datum
+jsonpath_pl_jsonpath(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonpath(fcinfo, jpiAdd);
+}
+
+/* Implementation of operator jsonpath + jsonb */
+Datum
+jsonpath_pl_jsonb(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonb(fcinfo, jpiAdd);
+}
+
+/* Implementation of operator jsonpath - jsonpath */
+Datum
+jsonpath_mi_jsonpath(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonpath(fcinfo, jpiSub);
+}
+
+/* Implementation of operator jsonpath - jsonb */
+Datum
+jsonpath_mi_jsonb(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonb(fcinfo, jpiSub);
+}
+
+/* Implementation of operator jsonpath / jsonpath */
+Datum
+jsonpath_mul_jsonpath(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonpath(fcinfo, jpiMul);
+}
+
+/* Implementation of operator jsonpath * jsonb */
+Datum
+jsonpath_mul_jsonb(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonb(fcinfo, jpiMul);
+}
+
+/* Implementation of operator jsonpath / jsonpath */
+Datum
+jsonpath_div_jsonpath(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonpath(fcinfo, jpiDiv);
+}
+
+/* Implementation of operator jsonpath / jsonb */
+Datum
+jsonpath_div_jsonb(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonb(fcinfo, jpiDiv);
+}
+
+/* Implementation of operator jsonpath % jsonpath */
+Datum
+jsonpath_mod_jsonpath(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonpath(fcinfo, jpiMod);
+}
+
+/* Implementation of operator jsonpath % jsonb */
+Datum
+jsonpath_mod_jsonb(PG_FUNCTION_ARGS)
+{
+ return jsonpath_op_jsonb(fcinfo, jpiMod);
+}
+
+/* Implementation of operator jsonpath -> text */
+Datum
+jsonpath_object_field(PG_FUNCTION_ARGS)
+{
+ JsonPath *jpObj = PG_GETARG_JSONPATH_P(0);
+ text *jpFld = PG_GETARG_TEXT_PP(1);
+ JsonPathParseItem jpiObj;
+ JsonPathParseItem jpiFld;
+
+ jspInitParseItemBin(&jpiObj, jpObj, &jpiFld);
+ jspInitParseItemString(&jpiFld, jpiKey,
+ VARDATA_ANY(jpFld), VARSIZE_ANY_EXHDR(jpFld), NULL);
+
+ PG_RETURN_JSONPATH_P(encodeJsonPath(&jpiObj,
+ (jpObj->header & JSONPATH_LAX) != 0,
+ INTALIGN(VARSIZE(jpObj)) + 8 +
+ jpiFld.value.string.len, NULL));
+}
+
+/* Implementation of operator jsonpath -> int */
+Datum
+jsonpath_array_element(PG_FUNCTION_ARGS)
+{
+ JsonPath *arr = PG_GETARG_JSONPATH_P(0);
+ int32 idx = PG_GETARG_INT32(1);
+ JsonPathParseItem jpiArr;
+ JsonPathParseItem jpiArrIdx;
+ JsonPathParseItem jpiIdx;
+ struct JsonPathParseArraySubscript subscript;
+
+ jspInitParseItemBin(&jpiArr, arr, &jpiArrIdx);
+
+ jspInitParseItem(&jpiArrIdx, jpiIndexArray, NULL);
+ jpiArrIdx.value.array.nelems = 1;
+ jpiArrIdx.value.array.elems = &subscript;
+
+ subscript.from = &jpiIdx;
+ subscript.to = NULL;
+
+ jspInitParseItem(&jpiIdx, jpiNumeric, NULL);
+ jpiIdx.value.numeric = DatumGetNumeric(
+ DirectFunctionCall1(int4_numeric, Int32GetDatum(idx)));
+
+ PG_RETURN_JSONPATH_P(encodeJsonPath(&jpiArr,
+ (arr->header & JSONPATH_LAX) != 0,
+ INTALIGN(VARSIZE(arr)) + 28 +
+ VARSIZE(jpiIdx.value.numeric), NULL));
+}
+
+/* Implementation of operator jsonpath ? jsonpath */
+Datum
+jsonpath_filter(PG_FUNCTION_ARGS)
+{
+ JsonPath *jpRoot = PG_GETARG_JSONPATH_P(0);
+ JsonPath *jpFilter = PG_GETARG_JSONPATH_P(1);
+ JsonPathItem root;
+ JsonPathParseItem jppiRoot;
+ JsonPathParseItem jppiFilter;
+ JsonPathParseItem jppiFilterArg;
+
+ checkJsonPathArgsMismatch(jpRoot, jpFilter);
+
+ jspInit(&root, jpFilter);
+
+ if (!jspIsBooleanOp(root.type))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("jsonpath filter must be boolean expression")));
+
+ jspInitParseItemBin(&jppiRoot, jpRoot, &jppiFilter);
+ jspInitParseItemUnary(&jppiFilter, jpiFilter, NULL, &jppiFilterArg);
+ jspInitParseItemBin(&jppiFilterArg, jpFilter, NULL);
+
+ PG_RETURN_JSONPATH_P(encodeJsonPath(&jppiRoot,
+ (jpRoot->header & JSONPATH_LAX) != 0,
+ INTALIGN(VARSIZE(jpRoot)) + 12 +
+ VARSIZE(jpFilter), NULL));
+}
+
+static bool
+replaceVariableReference(JsonPathContext *cxt, JsonPathItem *var, int32 pos)
+{
+ JsonbValue name;
+ JsonbValue valuebuf;
+ JsonbValue *value;
+ JsonPathParseItem tmp;
+ JsonPathParseItem *item;
+
+ name.type = jbvString;
+ name.val.string.val = jspGetString(var, &name.val.string.len);
+
+ value = findJsonbValueFromContainer(&cxt->vars->root, JB_FOBJECT, &name,
+ &valuebuf);
+
+ if (!value)
+ return false;
+
+ cxt->buf->len = pos + JSONPATH_HDRSZ; /* reset buffer */
+
+ item = jspInitParseItemJsonb(&tmp, JsonbValueToJsonb(value));
+
+ flattenJsonPathParseItem(cxt, item, false, false);
+
+ return true;
+}
+
+/* Implementation of operator jsonpath @ jsonb */
+Datum
+jsonpath_bind_jsonb(PG_FUNCTION_ARGS)
+{
+ JsonPath *jpRoot = PG_GETARG_JSONPATH_P(0);
+ Jsonb *jbVars = PG_GETARG_JSONB_P(1);
+ JsonPathParseItem jppiRoot;
+
+ jspInitParseItemBin(&jppiRoot, jpRoot, NULL);
+
+ PG_RETURN_JSONPATH_P(encodeJsonPath(&jppiRoot,
+ (jpRoot->header & JSONPATH_LAX) != 0,
+ INTALIGN(VARSIZE(jpRoot)) +
+ VARSIZE(jbVars) * 2, jbVars));
+}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 873d64b630..3938113841 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -61,54 +61,28 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "executor/execExpr.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parse_func.h"
#include "regex/regex.h"
#include "utils/builtins.h"
+#include "utils/date.h"
+#include "utils/datetime.h"
#include "utils/datum.h"
#include "utils/formatting.h"
#include "utils/float.h"
#include "utils/guc.h"
#include "utils/json.h"
+#include "utils/jsonapi.h"
#include "utils/jsonpath.h"
-#include "utils/date.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
#include "utils/timestamp.h"
#include "utils/varlena.h"
-
-/*
- * Represents "base object" and it's "id" for .keyvalue() evaluation.
- */
-typedef struct JsonBaseObjectInfo
-{
- JsonbContainer *jbc;
- int id;
-} JsonBaseObjectInfo;
-
-/*
- * Context of jsonpath execution.
- */
-typedef struct JsonPathExecContext
-{
- Jsonb *vars; /* variables to substitute into jsonpath */
- JsonbValue *root; /* for $ evaluation */
- JsonbValue *current; /* for @ evaluation */
- JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue()
- * evaluation */
- int lastGeneratedObjectId; /* "id" counter for .keyvalue()
- * evaluation */
- int innermostArraySize; /* for LAST array index evaluation */
- bool laxMode; /* true for "lax" mode, false for "strict"
- * mode */
- bool ignoreStructuralErrors; /* with "true" structural errors such
- * as absence of required json item or
- * unexpected json item type are
- * ignored */
- bool throwErrors; /* with "false" all suppressible errors are
- * suppressed */
-} JsonPathExecContext;
-
/* Context for LIKE_REGEX execution. */
typedef struct JsonLikeRegexContext
{
@@ -116,45 +90,100 @@ typedef struct JsonLikeRegexContext
int cflags;
} JsonLikeRegexContext;
-/* Result of jsonpath predicate evaluation */
-typedef enum JsonPathBool
+typedef struct JsonLambdaVar
{
- jpbFalse = 0,
- jpbTrue = 1,
- jpbUnknown = 2
-} JsonPathBool;
+ char *name;
+ JsonItem *val;
+} JsonLambdaVar;
-/* Result of jsonpath expression evaluation */
-typedef enum JsonPathExecResult
+typedef struct JsonLambdaVars
{
- jperOk = 0,
- jperNotFound = 1,
- jperError = 2
-} JsonPathExecResult;
+ int nvars;
+ JsonLambdaVar *vars;
+ void *parentVars;
+ JsonPathVarCallback parentGetVar;
+} JsonLambdaVars;
-#define jperIsError(jper) ((jper) == jperError)
+typedef union JsonLambdaCache
+{
+ JsonLambdaArg *args;
+ JsonLambdaVars vars;
+} JsonLambdaCache;
/*
- * List of jsonb values with shortcut for single-value list.
+ * Context for execution of
+ * jsonb_path_*(jsonb, jsonpath [, vars jsonb, silent boolean]) user functions.
*/
-typedef struct JsonValueList
+typedef struct JsonPathUserFuncContext
{
- JsonbValue *singleton;
- List *list;
-} JsonValueList;
-
-typedef struct JsonValueListIterator
+ FunctionCallInfo fcinfo;
+ void *js; /* first jsonb function argument */
+ Json *json;
+ JsonPath *jp; /* second jsonpath function argument */
+ void *vars; /* third vars function argument */
+ JsonValueList found; /* resulting item list */
+ bool silent; /* error suppression flag */
+} JsonPathUserFuncContext;
+
+typedef struct JsonpathQueryContext
{
- JsonbValue *value;
- ListCell *next;
-} JsonValueListIterator;
+ void *cache; /* jsonpath executor cache */
+ FuncCallContext *srfcxt; /* SRF context */
+} JsonpathQueryContext;
+
+/* Structures for JSON_TABLE execution */
+typedef struct JsonTableScanState JsonTableScanState;
+typedef struct JsonTableJoinState JsonTableJoinState;
-/* strict/lax flags is decomposed into four [un]wrap/error flags */
-#define jspStrictAbsenseOfErrors(cxt) (!(cxt)->laxMode)
-#define jspAutoUnwrap(cxt) ((cxt)->laxMode)
-#define jspAutoWrap(cxt) ((cxt)->laxMode)
-#define jspIgnoreStructuralErrors(cxt) ((cxt)->ignoreStructuralErrors)
-#define jspThrowErrors(cxt) ((cxt)->throwErrors)
+struct JsonTableScanState
+{
+ JsonTableScanState *parent;
+ JsonTableJoinState *nested;
+ MemoryContext mcxt;
+ JsonPath *path;
+ List *args;
+ JsonValueList found;
+ JsonValueListIterator iter;
+ Datum current;
+ int ordinal;
+ bool currentIsNull;
+ bool outerJoin;
+ bool errorOnError;
+ bool advanceNested;
+ bool reset;
+};
+
+struct JsonTableJoinState
+{
+ union
+ {
+ struct
+ {
+ JsonTableJoinState *left;
+ JsonTableJoinState *right;
+ bool cross;
+ bool advanceRight;
+ } join;
+ JsonTableScanState scan;
+ } u;
+ bool is_join;
+};
+
+/* random number to identify JsonTableContext */
+#define JSON_TABLE_CONTEXT_MAGIC 418352867
+
+typedef struct JsonTableContext
+{
+ int magic;
+ struct
+ {
+ ExprState *expr;
+ JsonTableScanState *scan;
+ } *colexprs;
+ JsonTableScanState root;
+ bool empty;
+ bool isJsonb;
+} JsonTableContext;
/* Convenience macro: return or throw error depending on context */
#define RETURN_ERROR(throw_error) \
@@ -166,92 +195,167 @@ do { \
} while (0)
typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
- JsonbValue *larg,
- JsonbValue *rarg,
+ JsonItem *larg,
+ JsonItem *rarg,
void *param);
-typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
-static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
- Jsonb *json, bool throwErrors, JsonValueList *result);
+typedef Numeric (*BinaryNumericFunc) (Numeric num1, Numeric num2, bool *error);
+typedef float8 (*BinaryDoubleFunc) (float8 num1, float8 num2, bool *error);
+
+typedef JsonbValue *(*JsonBuilderFunc) (JsonbParseState **,
+ JsonbIteratorToken,
+ JsonbValue *);
+
+static float8 float8_mod_error(float8 val1, float8 val2, bool *error);
+
+static void freeUserFuncContext(JsonPathUserFuncContext *cxt);
+static JsonPathExecResult executeUserFunc(FunctionCallInfo fcinfo,
+ JsonPathUserFuncContext *cxt, bool isJsonb, bool copy,
+ void **fn_extra);
+
+static JsonPathExecResult executeJsonPath(JsonPath *path, void *vars,
+ JsonPathVarCallback getVar,
+ Jsonx *json, bool isJsonb,
+ bool throwErrors,
+ JsonValueList *result,
+ void **pCache,
+ MemoryContext cacheCxt);
static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+ JsonPathItem *jsp, JsonItem *jb,
+ JsonValueList *found);
static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb,
- JsonValueList *found, bool unwrap);
+ JsonPathItem *jsp,
+ JsonItem *jb,
+ JsonValueList *found,
+ bool unwrap);
static JsonPathExecResult executeItemUnwrapTargetArray(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb,
- JsonValueList *found, bool unwrapElements);
+ JsonPathItem *jsp,
+ JsonItem *jb,
+ JsonValueList *found,
+ bool unwrapElements);
static JsonPathExecResult executeNextItem(JsonPathExecContext *cxt,
JsonPathItem *cur, JsonPathItem *next,
- JsonbValue *v, JsonValueList *found, bool copy);
-static JsonPathExecResult executeItemOptUnwrapResult(
- JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
- bool unwrap, JsonValueList *found);
-static JsonPathExecResult executeItemOptUnwrapResultNoThrow(
- JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonbValue *jb, bool unwrap, JsonValueList *found);
-static JsonPathBool executeBoolItem(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb, bool canHaveNext);
+ JsonItem *v, JsonValueList *found,
+ bool copy);
+static JsonPathExecResult executeItemOptUnwrapResult(JsonPathExecContext *cxt,
+ JsonPathItem *jsp,
+ JsonItem *jb, bool unwrap,
+ JsonValueList *found);
+static JsonPathExecResult executeItemOptUnwrapResultNoThrow(JsonPathExecContext *cxt,
+ JsonPathItem *jsp,
+ JsonItem *jb,
+ bool unwrap,
+ JsonValueList *found);
+static JsonPathBool executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, bool canHaveNext);
static JsonPathBool executeNestedBoolItem(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb);
+ JsonPathItem *jsp, JsonItem *jb);
static JsonPathExecResult executeAnyItem(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbContainer *jbc, JsonValueList *found,
- uint32 level, uint32 first, uint32 last,
+ bool outPath, uint32 level, uint32 first, uint32 last,
bool ignoreStructuralErrors, bool unwrapNext);
static JsonPathBool executePredicate(JsonPathExecContext *cxt,
- JsonPathItem *pred, JsonPathItem *larg, JsonPathItem *rarg,
- JsonbValue *jb, bool unwrapRightArg,
- JsonPathPredicateCallback exec, void *param);
+ JsonPathItem *pred, JsonPathItem *larg,
+ JsonPathItem *rarg, JsonItem *jb,
+ bool unwrapRightArg,
+ JsonPathPredicateCallback exec,
+ void *param);
static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb,
- BinaryArithmFunc func, JsonValueList *found);
+ JsonPathItem *jsp,
+ JsonItem *jb,
+ BinaryNumericFunc numFunc,
+ BinaryDoubleFunc dblFunc,
+ JsonValueList *found);
static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb, PGFunction func,
+ JsonPathItem *jsp,
+ JsonItem *jb,
+ PGFunction numFunc,
+ PGFunction dblFunc,
JsonValueList *found);
-static JsonPathBool executeStartsWith(JsonPathItem *jsp,
- JsonbValue *whole, JsonbValue *initial, void *param);
-static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
- JsonbValue *rarg, void *param);
+static JsonPathBool executeStartsWith(JsonPathItem *jsp, JsonItem *whole,
+ JsonItem *initial, void *param);
+static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonItem *str,
+ JsonItem *rarg, void *param);
static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
+ JsonPathItem *jsp,
+ JsonItem *jb, bool unwrap,
+ PGFunction numericFunc,
+ PGFunction doubleFunc,
JsonValueList *found);
static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+ JsonPathItem *jsp, JsonItem *jb,
+ JsonValueList *found);
static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonValueList *found, JsonPathBool res);
static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
- JsonbValue *value);
+ JsonItem *value);
static void getJsonPathVariable(JsonPathExecContext *cxt,
- JsonPathItem *variable, Jsonb *vars, JsonbValue *value);
-static int JsonbArraySize(JsonbValue *jb);
-static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
- JsonbValue *rv, void *p);
-static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2);
+ JsonPathItem *variable, JsonItem *value);
+static int getJsonPathVariableFromJsonx(void *varsJsonb, bool isJsonb,
+ char *varName, int varNameLen,
+ JsonItem *val, JsonbValue *baseObject);
+static JsonPathBool executeComparison(JsonPathItem *cmp, JsonItem *lv,
+ JsonItem *rv, void *p);
static int compareNumeric(Numeric a, Numeric b);
-static JsonbValue *copyJsonbValue(JsonbValue *src);
+static JsonPathExecResult executeFunction(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonItem *js,
+ JsonValueList *result /*, bool needBool */);
+
+static void JsonItemInitNull(JsonItem *item);
+static void JsonItemInitBool(JsonItem *item, bool val);
+static void JsonItemInitNumeric(JsonItem *item, Numeric val);
+#define JsonItemInitNumericDatum(item, val) \
+ JsonItemInitNumeric(item, DatumGetNumeric(val))
+static void JsonItemInitDouble(JsonItem *item, float8 val);
+static void JsonItemInitString(JsonItem *item, char *str, int len);
+static void JsonItemInitDatetime(JsonItem *item, Datum val, Oid typid,
+ int32 typmod, int tz);
+
+static JsonbValue *JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv);
+static const char *JsonItemTypeName(JsonItem *jsi);
static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb, int32 *index);
+ JsonPathItem *jsp, JsonItem *jb,
+ int32 *index);
static JsonBaseObjectInfo setBaseObject(JsonPathExecContext *cxt,
- JsonbValue *jbv, int32 id);
-static void JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv);
-static int JsonValueListLength(const JsonValueList *jvl);
-static bool JsonValueListIsEmpty(JsonValueList *jvl);
-static JsonbValue *JsonValueListHead(JsonValueList *jvl);
-static List *JsonValueListGetList(JsonValueList *jvl);
-static void JsonValueListInitIterator(const JsonValueList *jvl,
- JsonValueListIterator *it);
-static JsonbValue *JsonValueListNext(const JsonValueList *jvl,
- JsonValueListIterator *it);
-static int JsonbType(JsonbValue *jb);
+ JsonItem *jsi, int32 id);
static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
-static int JsonbType(JsonbValue *jb);
-static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
-static JsonbValue *wrapItemsInArray(const JsonValueList *items);
+static inline JsonbValue *JsonInitBinary(JsonbValue *jbv, Json *js);
+static JsonItem *getScalar(JsonItem *scalar, enum jbvType type);
+static JsonItem *getNumber(JsonItem *scalar);
+static bool convertJsonDoubleToNumeric(JsonItem *dbl, JsonItem *num);
+static JsonbValue *wrapItemsInArray(const JsonValueList *items, bool isJsonb);
+static text *JsonItemUnquoteText(JsonItem *jsi, bool isJsonb);
+static JsonItem *wrapJsonObjectOrArray(JsonItem *js, JsonItem *buf,
+ bool isJsonb);
+static void appendWrappedItems(JsonValueList *found, JsonValueList *items,
+ bool isJsonb);
+static JsonValueList prependKey(char *keystr, int keylen,
+ const JsonValueList *items, bool isJsonb);
+
+static JsonItem *getJsonObjectKey(JsonItem *jb, char *keystr, int keylen,
+ bool isJsonb, JsonItem *res);
+static JsonItem *getJsonArrayElement(JsonItem *jb, uint32 index, bool isJsonb,
+ JsonItem *res);
+
+static JsonbValue *JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv);
+static Jsonx *JsonbValueToJsonx(JsonbValue *jbv, bool isJsonb);
+
+static bool tryToParseDatetime(text *fmt, text *datetime, char *tzname,
+ bool strict, Datum *value, Oid *typid,
+ int32 *typmod, int *tzp, bool throwErrors);
+static int compareDatetime(Datum val1, Oid typid1, int tz1,
+ Datum val2, Oid typid2, int tz2,
+ bool *error);
+
+static JsonTableJoinState *JsonTableInitPlanState(JsonTableContext *cxt,
+ Node *plan, JsonTableScanState *parent);
+static bool JsonTableNextRow(JsonTableScanState *scan, bool isJsonb);
+
/****************** User interface to JsonPath executor ********************/
/*
- * jsonb_path_exists
+ * json[b]_path_exists
* Returns true if jsonpath returns at least one item for the specified
* jsonb value. This function and jsonb_path_match() are used to
* implement @? and @@ operators, which in turn are intended to have an
@@ -262,25 +366,11 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items);
* SQL/JSON. Regarding jsonb_path_match(), this function doesn't have
* an analogy in SQL/JSON, so we define its behavior on our own.
*/
-Datum
-jsonb_path_exists(PG_FUNCTION_ARGS)
+static Datum
+jsonx_path_exists(PG_FUNCTION_ARGS, bool isJsonb)
{
- Jsonb *jb = PG_GETARG_JSONB_P(0);
- JsonPath *jp = PG_GETARG_JSONPATH_P(1);
- JsonPathExecResult res;
- Jsonb *vars = NULL;
- bool silent = true;
-
- if (PG_NARGS() == 4)
- {
- vars = PG_GETARG_JSONB_P(2);
- silent = PG_GETARG_BOOL(3);
- }
-
- res = executeJsonPath(jp, vars, jb, !silent, NULL);
-
- PG_FREE_IF_COPY(jb, 0);
- PG_FREE_IF_COPY(jp, 1);
+ JsonPathExecResult res = executeUserFunc(fcinfo, NULL, isJsonb, false,
+ &fcinfo->flinfo->fn_extra);
if (jperIsError(res))
PG_RETURN_NULL();
@@ -288,55 +378,62 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(res == jperOk);
}
+Datum
+jsonb_path_exists(PG_FUNCTION_ARGS)
+{
+ return jsonx_path_exists(fcinfo, true);
+}
+
+Datum
+json_path_exists(PG_FUNCTION_ARGS)
+{
+ return jsonx_path_exists(fcinfo, false);
+}
+
/*
- * jsonb_path_exists_opr
- * Implementation of operator "jsonb @? jsonpath" (2-argument version of
- * jsonb_path_exists()).
+ * json[b]_path_exists_opr
+ * Implementation of operator "json[b] @? jsonpath" (2-argument version of
+ * json[b]_path_exists()).
*/
Datum
jsonb_path_exists_opr(PG_FUNCTION_ARGS)
{
- /* just call the other one -- it can handle both cases */
- return jsonb_path_exists(fcinfo);
+ return jsonx_path_exists(fcinfo, true);
+}
+
+Datum
+json_path_exists_opr(PG_FUNCTION_ARGS)
+{
+ return jsonx_path_exists(fcinfo, false);
}
/*
- * jsonb_path_match
+ * json[b]_path_match
* Returns jsonpath predicate result item for the specified jsonb value.
* See jsonb_path_exists() comment for details regarding error handling.
*/
-Datum
-jsonb_path_match(PG_FUNCTION_ARGS)
+static Datum
+jsonx_path_match(PG_FUNCTION_ARGS, bool isJsonb)
{
- Jsonb *jb = PG_GETARG_JSONB_P(0);
- JsonPath *jp = PG_GETARG_JSONPATH_P(1);
- JsonValueList found = {0};
- Jsonb *vars = NULL;
- bool silent = true;
-
- if (PG_NARGS() == 4)
- {
- vars = PG_GETARG_JSONB_P(2);
- silent = PG_GETARG_BOOL(3);
- }
+ JsonPathUserFuncContext cxt;
- (void) executeJsonPath(jp, vars, jb, !silent, &found);
+ (void) executeUserFunc(fcinfo, &cxt, isJsonb, false,
+ &fcinfo->flinfo->fn_extra);
- PG_FREE_IF_COPY(jb, 0);
- PG_FREE_IF_COPY(jp, 1);
+ freeUserFuncContext(&cxt);
- if (JsonValueListLength(&found) == 1)
+ if (JsonValueListLength(&cxt.found) == 1)
{
- JsonbValue *jbv = JsonValueListHead(&found);
+ JsonItem *res = JsonValueListHead(&cxt.found);
- if (jbv->type == jbvBool)
- PG_RETURN_BOOL(jbv->val.boolean);
+ if (JsonItemIsBool(res))
+ PG_RETURN_BOOL(JsonItemBool(res));
- if (jbv->type == jbvNull)
+ if (JsonItemIsNull(res))
PG_RETURN_NULL();
}
- if (!silent)
+ if (!cxt.silent)
ereport(ERROR,
(errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
errmsg("single boolean result is expected")));
@@ -344,110 +441,300 @@ jsonb_path_match(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+ return jsonx_path_match(fcinfo, true);
+}
+
+Datum
+json_path_match(PG_FUNCTION_ARGS)
+{
+ return jsonx_path_match(fcinfo, false);
+}
+
/*
- * jsonb_path_match_opr
- * Implementation of operator "jsonb @@ jsonpath" (2-argument version of
- * jsonb_path_match()).
+ * json[b]_path_match_opr
+ * Implementation of operator "json[b] @@ jsonpath" (2-argument version of
+ * json[b]_path_match()).
*/
Datum
jsonb_path_match_opr(PG_FUNCTION_ARGS)
{
/* just call the other one -- it can handle both cases */
- return jsonb_path_match(fcinfo);
+ return jsonx_path_match(fcinfo, true);
+}
+
+Datum
+json_path_match_opr(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonx_path_match(fcinfo, false);
}
/*
- * jsonb_path_query
+ * json[b]_path_query
* Executes jsonpath for given jsonb document and returns result as
* rowset.
*/
-Datum
-jsonb_path_query(PG_FUNCTION_ARGS)
+static Datum
+jsonx_path_query(PG_FUNCTION_ARGS, bool isJsonb)
{
+ JsonpathQueryContext *cxt = fcinfo->flinfo->fn_extra;
FuncCallContext *funcctx;
List *found;
- JsonbValue *v;
+ JsonItem *v;
ListCell *c;
+ Datum res;
+
+ if (!cxt)
+ cxt = fcinfo->flinfo->fn_extra =
+ MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt, sizeof(*cxt));
- if (SRF_IS_FIRSTCALL())
+ if (SRF_IS_FIRSTCALL_EXT(&cxt->srfcxt))
{
- JsonPath *jp;
- Jsonb *jb;
+ JsonPathUserFuncContext jspcxt;
MemoryContext oldcontext;
- Jsonb *vars;
- bool silent;
- JsonValueList found = {0};
- funcctx = SRF_FIRSTCALL_INIT();
+ funcctx = SRF_FIRSTCALL_INIT_EXT(&cxt->srfcxt);
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
- jb = PG_GETARG_JSONB_P_COPY(0);
- jp = PG_GETARG_JSONPATH_P_COPY(1);
- vars = PG_GETARG_JSONB_P_COPY(2);
- silent = PG_GETARG_BOOL(3);
-
- (void) executeJsonPath(jp, vars, jb, !silent, &found);
+ /* jsonb and jsonpath arguments are copied into SRF context. */
+ (void) executeUserFunc(fcinfo, &jspcxt, isJsonb, true, &cxt->cache);
- funcctx->user_fctx = JsonValueListGetList(&found);
+ /*
+ * Don't free jspcxt because items in jspcxt.found can reference
+ * untoasted copies of jsonb and jsonpath arguments.
+ */
+ funcctx->user_fctx = JsonValueListGetList(&jspcxt.found);
MemoryContextSwitchTo(oldcontext);
}
- funcctx = SRF_PERCALL_SETUP();
+ funcctx = SRF_PERCALL_SETUP_EXT(&cxt->srfcxt);
found = funcctx->user_fctx;
c = list_head(found);
if (c == NULL)
- SRF_RETURN_DONE(funcctx);
+ SRF_RETURN_DONE_EXT(funcctx, &cxt->srfcxt);
v = lfirst(c);
funcctx->user_fctx = list_delete_first(found);
- SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
+ res = isJsonb ?
+ JsonbPGetDatum(JsonItemToJsonb(v)) :
+ JsonPGetDatum(JsonItemToJson(v));
+
+ SRF_RETURN_NEXT(funcctx, res);
+}
+
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+ return jsonx_path_query(fcinfo, true);
+}
+
+Datum
+json_path_query(PG_FUNCTION_ARGS)
+{
+ return jsonx_path_query(fcinfo, false);
}
/*
- * jsonb_path_query_array
+ * json[b]_path_query_array
* Executes jsonpath for given jsonb document and returns result as
* jsonb array.
*/
+static Datum
+jsonx_path_query_array(PG_FUNCTION_ARGS, bool isJsonb)
+{
+ JsonPathUserFuncContext cxt;
+ Datum res;
+
+ (void) executeUserFunc(fcinfo, &cxt, isJsonb, false,
+ &fcinfo->flinfo->fn_extra);
+
+ res = JsonbValueToJsonxDatum(wrapItemsInArray(&cxt.found, isJsonb), isJsonb);
+
+ freeUserFuncContext(&cxt);
+
+ PG_RETURN_DATUM(res);
+}
+
Datum
jsonb_path_query_array(PG_FUNCTION_ARGS)
{
- Jsonb *jb = PG_GETARG_JSONB_P(0);
- JsonPath *jp = PG_GETARG_JSONPATH_P(1);
- JsonValueList found = {0};
- Jsonb *vars = PG_GETARG_JSONB_P(2);
- bool silent = PG_GETARG_BOOL(3);
-
- (void) executeJsonPath(jp, vars, jb, !silent, &found);
+ return jsonx_path_query_array(fcinfo, true);
+}
- PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
+Datum
+json_path_query_array(PG_FUNCTION_ARGS)
+{
+ return jsonx_path_query_array(fcinfo, false);
}
/*
- * jsonb_path_query_first
+ * json[b]_path_query_first
* Executes jsonpath for given jsonb document and returns first result
* item. If there are no items, NULL returned.
*/
+static Datum
+jsonx_path_query_first(PG_FUNCTION_ARGS, bool isJsonb)
+{
+ JsonPathUserFuncContext cxt;
+ Datum res;
+
+ (void) executeUserFunc(fcinfo, &cxt, isJsonb, false,
+ &fcinfo->flinfo->fn_extra);
+
+ if (JsonValueListLength(&cxt.found) >= 1)
+ res = JsonItemToJsonxDatum(JsonValueListHead(&cxt.found), isJsonb);
+ else
+ res = (Datum) 0;
+
+ freeUserFuncContext(&cxt);
+
+ if (res)
+ PG_RETURN_DATUM(res);
+ else
+ PG_RETURN_NULL();
+}
+
Datum
jsonb_path_query_first(PG_FUNCTION_ARGS)
{
- Jsonb *jb = PG_GETARG_JSONB_P(0);
- JsonPath *jp = PG_GETARG_JSONPATH_P(1);
- JsonValueList found = {0};
- Jsonb *vars = PG_GETARG_JSONB_P(2);
- bool silent = PG_GETARG_BOOL(3);
+ return jsonx_path_query_first(fcinfo, true);
+}
+
+Datum
+json_path_query_first(PG_FUNCTION_ARGS)
+{
+ return jsonx_path_query_first(fcinfo, false);
+}
+
+/*
+ * json[b]_path_query_first_text
+ * Executes jsonpath for given jsonb document and returns first result
+ * item as text. If there are no items, NULL returned.
+ */
+static Datum
+jsonx_path_query_first_text(PG_FUNCTION_ARGS, bool isJsonb)
+{
+ JsonPathUserFuncContext cxt;
+ text *txt;
+
+ (void) executeUserFunc(fcinfo, &cxt, isJsonb, false,
+ &fcinfo->flinfo->fn_extra);
- (void) executeJsonPath(jp, vars, jb, !silent, &found);
+ if (JsonValueListLength(&cxt.found) >= 1)
+ txt = JsonItemUnquoteText(JsonValueListHead(&cxt.found), isJsonb);
+ else
+ txt = NULL;
+
+ freeUserFuncContext(&cxt);
- if (JsonValueListLength(&found) >= 1)
- PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
+ if (txt)
+ PG_RETURN_TEXT_P(txt);
else
PG_RETURN_NULL();
}
+Datum
+jsonb_path_query_first_text(PG_FUNCTION_ARGS)
+{
+ return jsonx_path_query_first_text(fcinfo, true);
+}
+
+Datum
+json_path_query_first_text(PG_FUNCTION_ARGS)
+{
+ return jsonx_path_query_first_text(fcinfo, false);
+}
+
+/* Free untoasted copies of jsonb and jsonpath arguments. */
+static void
+freeUserFuncContext(JsonPathUserFuncContext *cxt)
+{
+ FunctionCallInfo fcinfo = cxt->fcinfo;
+
+ PG_FREE_IF_COPY(cxt->js, 0);
+ PG_FREE_IF_COPY(cxt->jp, 1);
+ if (cxt->vars)
+ PG_FREE_IF_COPY(cxt->vars, 2);
+ if (cxt->json)
+ pfree(cxt->json);
+}
+
+/*
+ * Common code for jsonb_path_*(jsonb, jsonpath [, vars jsonb, silent bool])
+ * user functions.
+ *
+ * 'copy' flag enables copying of first three arguments into the current memory
+ * context.
+ */
+static JsonPathExecResult
+executeUserFunc(FunctionCallInfo fcinfo, JsonPathUserFuncContext *cxt,
+ bool isJsonb, bool copy, void **fn_extra)
+{
+ Datum js_toasted = PG_GETARG_DATUM(0);
+ struct varlena *js_detoasted = copy ?
+ PG_DETOAST_DATUM(js_toasted) :
+ PG_DETOAST_DATUM_COPY(js_toasted);
+ Jsonx *js = DatumGetJsonxP(js_detoasted, isJsonb);
+ JsonPath *jp = copy ? PG_GETARG_JSONPATH_P_COPY(1) : PG_GETARG_JSONPATH_P(1);
+ struct varlena *vars_detoasted = NULL;
+ Jsonx *vars = NULL;
+ bool silent = true;
+ JsonPathExecResult res;
+
+ if (PG_NARGS() == 4)
+ {
+ Datum vars_toasted = PG_GETARG_DATUM(2);
+
+ vars_detoasted = copy ?
+ PG_DETOAST_DATUM(vars_toasted) :
+ PG_DETOAST_DATUM_COPY(vars_toasted);
+
+ vars = DatumGetJsonxP(vars_detoasted, isJsonb);
+
+ silent = PG_GETARG_BOOL(3);
+ }
+
+ if (cxt)
+ {
+ cxt->fcinfo = fcinfo;
+ cxt->js = js_detoasted;
+ cxt->jp = jp;
+ cxt->vars = vars_detoasted;
+ cxt->json = isJsonb ? NULL : &js->js;
+ cxt->silent = silent;
+ memset(&cxt->found, 0, sizeof(cxt->found));
+ }
+
+ res = executeJsonPath(jp, vars, getJsonPathVariableFromJsonx,
+ js, isJsonb, !silent, cxt ? &cxt->found : NULL,
+ fn_extra, fcinfo->flinfo->fn_mcxt);
+
+ if (!cxt && !copy)
+ {
+ PG_FREE_IF_COPY(js_detoasted, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (vars_detoasted)
+ PG_FREE_IF_COPY(vars_detoasted, 2);
+
+ if (!isJsonb)
+ {
+ pfree(js);
+ if (vars)
+ pfree(vars);
+ }
+ }
+
+ return res;
+}
+
/********************Execute functions for JsonPath**************************/
/*
@@ -470,37 +757,106 @@ jsonb_path_query_first(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)
+executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar,
+ Jsonx *json, bool isJsonb, bool throwErrors,
+ JsonValueList *result, void **pCache, MemoryContext cacheCxt)
{
JsonPathExecContext cxt;
JsonPathExecResult res;
JsonPathItem jsp;
- JsonbValue jbv;
+ JsonItem jsi;
+ JsonbValue *jbv = JsonItemJbv(&jsi);
+ JsonItemStackEntry root;
- jspInit(&jsp, path);
+ if (path->ext_items_count)
+ {
+ if (pCache)
+ {
+ struct
+ {
+ JsonPath *path;
+ void **cache;
+ } *cache = *pCache;
+
+ if (!cache)
+ cache = *pCache = MemoryContextAllocZero(cacheCxt, sizeof(*cache));
+
+ if (cache->path &&
+ (VARSIZE(path) != VARSIZE(cache->path) ||
+ memcmp(path, cache->path, VARSIZE(path))))
+ {
+ /* invalidate cache TODO optimize */
+ cache->path = NULL;
+
+ if (cache->cache)
+ {
+ pfree(cache->cache);
+ cache->cache = NULL;
+ }
+ }
+
+ if (cache->path)
+ {
+ Assert(cache->cache);
+ }
+ else
+ {
+ Assert(!cache->cache);
+
+ cache->path = MemoryContextAlloc(cacheCxt, VARSIZE(path));
+ memcpy(cache->path, path, VARSIZE(path));
+
+ cache->cache = MemoryContextAllocZero(cacheCxt,
+ sizeof(cxt.cache[0]) *
+ path->ext_items_count);
+ }
+
+ cxt.cache = cache->cache;
+ cxt.cache_mcxt = cacheCxt;
+
+ path = cache->path; /* use cached jsonpath value */
+ }
+ else
+ {
+ cxt.cache = palloc0(sizeof(cxt.cache[0]) * path->ext_items_count);
+ cxt.cache_mcxt = CurrentMemoryContext;
+ }
+ }
+ else
+ {
+ cxt.cache = NULL;
+ cxt.cache_mcxt = NULL;
+ }
- if (!JsonbExtractScalar(&json->root, &jbv))
- JsonbInitBinary(&jbv, json);
+ jspInit(&jsp, path);
- if (vars && !JsonContainerIsObject(&vars->root))
+ if (isJsonb)
{
- 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.")));
+ if (!JsonbExtractScalar(&json->jb.root, jbv))
+ JsonbInitBinary(jbv, &json->jb);
+ }
+ else
+ {
+ if (!JsonExtractScalar(&json->js.root, jbv))
+ JsonInitBinary(jbv, &json->js);
}
cxt.vars = vars;
+ cxt.getVar = getVar;
+ cxt.args = NULL;
cxt.laxMode = (path->header & JSONPATH_LAX) != 0;
cxt.ignoreStructuralErrors = cxt.laxMode;
- cxt.root = &jbv;
- cxt.current = &jbv;
+ cxt.root = &jsi;
+ cxt.stack = NULL;
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, isJsonb, NULL, 0, NULL, NULL);
cxt.innermostArraySize = -1;
cxt.throwErrors = throwErrors;
+ cxt.isJsonb = isJsonb;
+
+ pushJsonItem(&cxt.stack, &root, cxt.root, &cxt.baseObject);
if (jspStrictAbsenseOfErrors(&cxt) && !result)
{
@@ -510,7 +866,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
*/
JsonValueList vals = {0};
- res = executeItem(&cxt, &jsp, &jbv, &vals);
+ res = executeItem(&cxt, &jsp, &jsi, &vals);
if (jperIsError(res))
return res;
@@ -518,7 +874,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
}
- res = executeItem(&cxt, &jsp, &jbv, result);
+ res = executeItem(&cxt, &jsp, &jsi, result);
Assert(!throwErrors || !jperIsError(res));
@@ -530,11 +886,18 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
*/
static JsonPathExecResult
executeItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonbValue *jb, JsonValueList *found)
+ JsonItem *jb, JsonValueList *found)
{
return executeItemOptUnwrapTarget(cxt, jsp, jb, found, jspAutoUnwrap(cxt));
}
+JsonPathExecResult
+jspExecuteItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, JsonValueList *found)
+{
+ return executeItem(cxt, jsp, jb, found);
+}
+
/*
* Main jsonpath executor function: walks on jsonpath structure, finds
* relevant parts of jsonb and evaluates expressions over them.
@@ -542,7 +905,7 @@ executeItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
*/
static JsonPathExecResult
executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonbValue *jb, JsonValueList *found, bool unwrap)
+ JsonItem *jb, JsonValueList *found, bool unwrap)
{
JsonPathItem elem;
JsonPathExecResult res = jperNotFound;
@@ -575,25 +938,36 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
}
case jpiKey:
- if (JsonbType(jb) == jbvObject)
+ if (JsonItemIsObject(jb))
{
- JsonbValue *v;
- JsonbValue key;
+ JsonItem obj;
- key.type = jbvString;
- key.val.string.val = jspGetString(jsp, &key.val.string.len);
+ jb = wrapJsonObjectOrArray(jb, &obj, cxt->isJsonb);
+ return executeItemOptUnwrapTarget(cxt, jsp, jb, found, unwrap);
+ }
+ else if (JsonItemIsBinary(jb) &&
+ JsonContainerIsObject(JsonItemBinary(jb).data))
+ {
+ JsonItem val;
+ int keylen;
+ char *key = jspGetString(jsp, &keylen);
- v = findJsonbValueFromContainer(jb->val.binary.data,
- JB_FOBJECT, &key);
+ jb = getJsonObjectKey(jb, key, keylen, cxt->isJsonb, &val);
- if (v != NULL)
+ if (jb != NULL)
{
- res = executeNextItem(cxt, jsp, NULL,
- v, found, false);
+ JsonValueList items = {0};
+ JsonValueList *pitems = found;
+
+ if (pitems && jspOutPath(jsp))
+ pitems = &items;
- /* free value if it was not added to found list */
- if (jspHasNext(jsp) || !found)
- pfree(v);
+ res = executeNextItem(cxt, jsp, NULL, jb, pitems, true);
+
+ if (!JsonValueListIsEmpty(&items) && !jperIsError(res))
+ JsonValueListConcat(found, prependKey(key, keylen,
+ &items,
+ cxt->isJsonb));
}
else if (!jspIgnoreStructuralErrors(cxt))
{
@@ -603,10 +977,9 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
return jperError;
ereport(ERROR,
- (errcode(ERRCODE_JSON_MEMBER_NOT_FOUND), \
+ (errcode(ERRCODE_JSON_MEMBER_NOT_FOUND),
errmsg("JSON object does not contain key \"%s\"",
- pnstrdup(key.val.string.val,
- key.val.string.len))));
+ pnstrdup(key, keylen))));
}
}
else if (unwrap && JsonbType(jb) == jbvArray)
@@ -628,17 +1001,47 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
break;
case jpiCurrent:
- res = executeNextItem(cxt, jsp, NULL, cxt->current,
+ res = executeNextItem(cxt, jsp, NULL, cxt->stack->item,
found, true);
break;
+ case jpiCurrentN:
+ {
+ JsonItemStackEntry *current = cxt->stack;
+ int i;
+
+ for (i = 0; i < jsp->content.current.level; i++)
+ {
+ current = current->parent;
+
+ if (!current)
+ elog(ERROR, "invalid jsonpath current item reference");
+ }
+
+ baseObject = cxt->baseObject;
+ cxt->baseObject = current->base;
+ jb = current->item;
+ res = executeNextItem(cxt, jsp, NULL, jb, found, true);
+ cxt->baseObject = baseObject;
+ break;
+ }
+
case jpiAnyArray:
if (JsonbType(jb) == jbvArray)
{
+ JsonValueList items = {0};
+ JsonValueList *pitems = found;
+ bool wrap = pitems && jspOutPath(jsp);
bool hasNext = jspGetNext(jsp, &elem);
+ if (wrap)
+ pitems = &items;
+
res = executeItemUnwrapTargetArray(cxt, hasNext ? &elem : NULL,
- jb, found, jspAutoUnwrap(cxt));
+ jb, pitems, jspAutoUnwrap(cxt));
+
+ if (wrap && !jperIsError(res))
+ appendWrappedItems(found, &items, cxt->isJsonb);
}
else if (jspAutoWrap(cxt))
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
@@ -649,45 +1052,186 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
break;
case jpiIndexArray:
- if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
+ if (JsonbType(jb) == jbvObject)
{
int innermostArraySize = cxt->innermostArraySize;
int i;
- int size = JsonbArraySize(jb);
- bool singleton = size < 0;
- bool hasNext = jspGetNext(jsp, &elem);
+ JsonItem bin;
+ JsonValueList items = {0};
+ JsonValueList *pitems = found;
+ bool wrap = pitems && jspOutPath(jsp);
- if (singleton)
- size = 1;
+ if (wrap)
+ pitems = &items;
- cxt->innermostArraySize = size; /* for LAST evaluation */
+ jb = wrapJsonObjectOrArray(jb, &bin, cxt->isJsonb);
+
+ cxt->innermostArraySize = 1;
for (i = 0; i < jsp->content.array.nelems; i++)
{
JsonPathItem from;
JsonPathItem to;
- int32 index;
- int32 index_from;
- int32 index_to;
- bool range = jspGetArraySubscript(jsp, &from,
- &to, i);
-
- res = getArrayIndex(cxt, &from, jb, &index_from);
-
- if (jperIsError(res))
- break;
+ JsonItem *key;
+ JsonValueList keys = { 0 };
+ bool range = jspGetArraySubscript(jsp, &from, &to, i);
if (range)
{
- res = getArrayIndex(cxt, &to, jb, &index_to);
+ int index_from;
+ int index_to;
+ if (!jspAutoWrap(cxt))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_JSON_ARRAY_NOT_FOUND),
+ errmsg("jsonpath array accessor can only be applied to an array"))));
+
+ res = getArrayIndex(cxt, &from, jb, &index_from);
if (jperIsError(res))
- break;
- }
- else
- index_to = index_from;
+ return res;
- if (!jspIgnoreStructuralErrors(cxt) &&
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+ if (jperIsError(res))
+ return res;
+
+ res = jperNotFound;
+
+ if (index_from <= 0 && index_to >= 0)
+ {
+ res = executeNextItem(cxt, jsp, NULL, jb, pitems,
+ true);
+ if (jperIsError(res))
+ return res;
+ }
+
+ if (res == jperOk && !found)
+ break;
+
+ continue;
+ }
+
+ res = executeItem(cxt, &from, jb, &keys);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&keys) != 1)
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
+ errmsg("object subscript must be a singleton value"))));
+
+ key = JsonValueListHead(&keys);
+ /* key = extractScalar(key, &tmp); XXX */
+
+ res = jperNotFound;
+
+ if (JsonItemIsNumeric(key) && jspAutoWrap(cxt))
+ {
+ Datum index_datum =
+ DirectFunctionCall2(numeric_trunc, JsonItemNumericDatum(key), Int32GetDatum(0));
+ bool have_error = false;
+ int index =
+ numeric_int4_opt_error(DatumGetNumeric(index_datum),
+ &have_error);
+
+ if (have_error)
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
+ errmsg("jsonpath array subscript is out integer range"))));
+
+ if (!index)
+ {
+ res = executeNextItem(cxt, jsp, NULL, jb, pitems,
+ true);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
+ errmsg("jsonpath array subscript is out of bounds"))));
+ }
+ else if (JsonItemIsString(key))
+ {
+ JsonItem valbuf;
+ JsonItem *val;
+
+ val = getJsonObjectKey(jb, JsonItemString(key).val,
+ JsonItemString(key).len,
+ cxt->isJsonb, &valbuf);
+
+ if (val)
+ {
+ res = executeNextItem(cxt, jsp, NULL, val, pitems,
+ true);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_JSON_MEMBER_NOT_FOUND),
+ errmsg("JSON object does not contain the specified key"))));
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
+ errmsg("object subscript must be a string or number"))));
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+
+ if (wrap && !jperIsError(res))
+ appendWrappedItems(found, &items, cxt->isJsonb);
+ }
+ else if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
+ {
+ int innermostArraySize = cxt->innermostArraySize;
+ int i;
+ int size = JsonxArraySize(jb, cxt->isJsonb);
+ bool binary = JsonItemIsBinary(jb);
+ bool singleton = size < 0;
+ JsonValueList items = {0};
+ JsonValueList *pitems = found;
+ bool wrap = pitems && jspOutPath(jsp);
+ bool hasNext = jspGetNext(jsp, &elem);
+
+ if (wrap)
+ pitems = &items;
+
+ if (singleton)
+ size = 1;
+
+ cxt->innermostArraySize = size; /* for LAST evaluation */
+
+ for (i = 0; i < jsp->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ int32 index;
+ int32 index_from;
+ int32 index_to;
+ bool range = jspGetArraySubscript(jsp, &from,
+ &to, i);
+
+ res = getArrayIndex(cxt, &from, jb, &index_from);
+
+ if (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!jspIgnoreStructuralErrors(cxt) &&
(index_from < 0 ||
index_from > index_to ||
index_to >= size))
@@ -705,30 +1249,32 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
for (index = index_from; index <= index_to; index++)
{
- JsonbValue *v;
- bool copy;
+ JsonItem jsibuf;
+ JsonItem *jsi;
if (singleton)
{
- v = jb;
- copy = true;
+ jsi = jb;
}
- else
+ else if (binary)
{
- v = getIthJsonbValueFromContainer(jb->val.binary.data,
- (uint32) index);
+ jsi = getJsonArrayElement(jb, (uint32) index,
+ cxt->isJsonb, &jsibuf);
- if (v == NULL)
+ if (jsi == NULL)
continue;
-
- copy = false;
+ }
+ else
+ {
+ jsi = JsonbValueToJsonItem(&JsonItemArray(jb).elems[index],
+ &jsibuf);
}
if (!hasNext && !found)
return jperOk;
- res = executeNextItem(cxt, jsp, &elem, v, found,
- copy);
+ res = executeNextItem(cxt, jsp, &elem, jsi, pitems,
+ true);
if (jperIsError(res))
break;
@@ -745,19 +1291,22 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
}
cxt->innermostArraySize = innermostArraySize;
+
+ if (wrap && !jperIsError(res))
+ appendWrappedItems(found, &items, cxt->isJsonb);
}
else if (!jspIgnoreStructuralErrors(cxt))
{
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_JSON_ARRAY_NOT_FOUND),
- errmsg("jsonpath array accessor can only be applied to an array"))));
+ errmsg("jsonpath array accessor can only be applied to an array or object"))));
}
break;
case jpiLast:
{
- JsonbValue tmpjbv;
- JsonbValue *lastjbv;
+ JsonItem tmpjsi;
+ JsonItem *lastjsi;
int last;
bool hasNext = jspGetNext(jsp, &elem);
@@ -772,29 +1321,27 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
last = cxt->innermostArraySize - 1;
- lastjbv = hasNext ? &tmpjbv : palloc(sizeof(*lastjbv));
+ lastjsi = hasNext ? &tmpjsi : palloc(sizeof(*lastjsi));
- lastjbv->type = jbvNumeric;
- lastjbv->val.numeric =
- DatumGetNumeric(DirectFunctionCall1(int4_numeric,
- Int32GetDatum(last)));
+ JsonItemInitNumericDatum(lastjsi,
+ DirectFunctionCall1(int4_numeric,
+ Int32GetDatum(last)));
- res = executeNextItem(cxt, jsp, &elem,
- lastjbv, found, hasNext);
+ res = executeNextItem(cxt, jsp, &elem, lastjsi, found, hasNext);
}
break;
case jpiAnyKey:
if (JsonbType(jb) == jbvObject)
{
+ JsonItem bin;
bool hasNext = jspGetNext(jsp, &elem);
- if (jb->type != jbvBinary)
- elog(ERROR, "invalid jsonb object type: %d", jb->type);
+ jb = wrapJsonObjectOrArray(jb, &bin, cxt->isJsonb);
return executeAnyItem
(cxt, hasNext ? &elem : NULL,
- jb->val.binary.data, found, 1, 1, 1,
+ JsonItemBinary(jb).data, found, jspOutPath(jsp), 1, 1, 1,
false, jspAutoUnwrap(cxt));
}
else if (unwrap && JsonbType(jb) == jbvArray)
@@ -810,30 +1357,35 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
case jpiAdd:
return executeBinaryArithmExpr(cxt, jsp, jb,
- numeric_add_opt_error, found);
+ numeric_add_opt_error,
+ float8_pl_error, found);
case jpiSub:
return executeBinaryArithmExpr(cxt, jsp, jb,
- numeric_sub_opt_error, found);
+ numeric_sub_opt_error,
+ float8_mi_error, found);
case jpiMul:
return executeBinaryArithmExpr(cxt, jsp, jb,
- numeric_mul_opt_error, found);
+ numeric_mul_opt_error,
+ float8_mul_error, found);
case jpiDiv:
return executeBinaryArithmExpr(cxt, jsp, jb,
- numeric_div_opt_error, found);
+ numeric_div_opt_error,
+ float8_div_error, found);
case jpiMod:
return executeBinaryArithmExpr(cxt, jsp, jb,
- numeric_mod_opt_error, found);
+ numeric_mod_opt_error,
+ float8_mod_error, found);
case jpiPlus:
- return executeUnaryArithmExpr(cxt, jsp, jb, NULL, found);
+ return executeUnaryArithmExpr(cxt, jsp, jb, NULL, NULL, found);
case jpiMinus:
return executeUnaryArithmExpr(cxt, jsp, jb, numeric_uminus,
- found);
+ float8um, found);
case jpiFilter:
{
@@ -855,8 +1407,11 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
case jpiAny:
{
+ JsonItem bin;
bool hasNext = jspGetNext(jsp, &elem);
+ jb = wrapJsonObjectOrArray(jb, &bin, cxt->isJsonb);
+
/* first try without any intermediate steps */
if (jsp->content.anybounds.first == 0)
{
@@ -864,18 +1419,17 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
savedIgnoreStructuralErrors = cxt->ignoreStructuralErrors;
cxt->ignoreStructuralErrors = true;
- res = executeNextItem(cxt, jsp, &elem,
- jb, found, true);
+ res = executeNextItem(cxt, jsp, &elem, jb, found, true);
cxt->ignoreStructuralErrors = savedIgnoreStructuralErrors;
if (res == jperOk && !found)
break;
}
- if (jb->type == jbvBinary)
+ if (JsonItemIsBinary(jb))
res = executeAnyItem
(cxt, hasNext ? &elem : NULL,
- jb->val.binary.data, found,
+ JsonItemBinary(jb).data, found, jspOutPath(jsp),
1,
jsp->content.anybounds.first,
jsp->content.anybounds.last,
@@ -888,9 +1442,10 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
case jpiNumeric:
case jpiString:
case jpiVariable:
+ case jpiArgument:
{
- JsonbValue vbuf;
- JsonbValue *v;
+ JsonItem vbuf;
+ JsonItem *v;
bool hasNext = jspGetNext(jsp, &elem);
if (!hasNext && !found)
@@ -898,7 +1453,6 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
res = jperOk; /* skip evaluation */
break;
}
-
v = hasNext ? &vbuf : palloc(sizeof(*v));
baseObject = cxt->baseObject;
@@ -912,20 +1466,18 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
case jpiType:
{
- JsonbValue *jbv = palloc(sizeof(*jbv));
+ JsonItem jsi;
+ const char *typname = JsonItemTypeName(jb);
- jbv->type = jbvString;
- jbv->val.string.val = pstrdup(JsonbTypeName(jb));
- jbv->val.string.len = strlen(jbv->val.string.val);
+ JsonItemInitString(&jsi, pstrdup(typname), strlen(typname));
- res = executeNextItem(cxt, jsp, NULL, jbv,
- found, false);
+ res = executeNextItem(cxt, jsp, NULL, &jsi, found, true);
}
break;
case jpiSize:
{
- int size = JsonbArraySize(jb);
+ int size = JsonxArraySize(jb, cxt->isJsonb);
if (size < 0)
{
@@ -944,10 +1496,8 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
jb = palloc(sizeof(*jb));
- jb->type = jbvNumeric;
- jb->val.numeric =
- DatumGetNumeric(DirectFunctionCall1(int4_numeric,
- Int32GetDatum(size)));
+ JsonItemInitNumericDatum(jb, DirectFunctionCall1(int4_numeric,
+ Int32GetDatum(size)));
res = executeNextItem(cxt, jsp, NULL, jb, found, false);
}
@@ -955,49 +1505,56 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
case jpiAbs:
return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_abs,
- found);
+ float8abs, found);
case jpiFloor:
return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_floor,
- found);
+ dfloor, found);
case jpiCeiling:
return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_ceil,
- found);
+ dceil, found);
case jpiDouble:
{
- JsonbValue jbv;
+ JsonItem jsi;
if (unwrap && JsonbType(jb) == jbvArray)
return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
false);
- if (jb->type == jbvNumeric)
+ if (JsonItemIsDouble(jb))
+ {
+ /* nothing to do */
+ }
+ else if (JsonItemIsNumeric(jb))
{
char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
- NumericGetDatum(jb->val.numeric)));
+ JsonItemNumericDatum(jb)));
bool have_error = false;
+ float8 val;
- (void) float8in_internal_opt_error(tmp,
- NULL,
- "double precision",
- tmp,
- &have_error);
+ val = float8in_internal_opt_error(tmp,
+ NULL,
+ "double precision",
+ tmp,
+ &have_error);
if (have_error)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
errmsg("jsonpath item method .%s() can only be applied to a numeric value",
jspOperationName(jsp->type)))));
- res = jperOk;
+
+ jb = &jsi;
+ JsonItemInitDouble(jb, val);
}
- else if (jb->type == jbvString)
+ else if (JsonItemIsString(jb))
{
/* cast string as double */
- double val;
- char *tmp = pnstrdup(jb->val.string.val,
- jb->val.string.len);
+ float8 val;
+ char *tmp = pnstrdup(JsonItemString(jb).val,
+ JsonItemString(jb).len);
bool have_error = false;
val = float8in_internal_opt_error(tmp,
@@ -1006,20 +1563,16 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
tmp,
&have_error);
- if (have_error || isinf(val))
+ if (have_error)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
errmsg("jsonpath item method .%s() can only be applied to a numeric value",
jspOperationName(jsp->type)))));
- jb = &jbv;
- jb->type = jbvNumeric;
- jb->val.numeric = DatumGetNumeric(DirectFunctionCall1(float8_numeric,
- Float8GetDatum(val)));
- res = jperOk;
+ jb = &jsi;
+ JsonItemInitDouble(jb, val);
}
-
- if (res == jperNotFound)
+ else
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
errmsg("jsonpath item method .%s() can only be applied to a string or numeric value",
@@ -1029,1246 +1582,3639 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
}
break;
- case jpiKeyValue:
- if (unwrap && JsonbType(jb) == jbvArray)
- return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+ case jpiDatetime:
+ {
+ JsonbValue jbvbuf;
+ Datum value;
+ text *datetime;
+ Oid typid;
+ int32 typmod = -1;
+ int tz = PG_INT32_MIN;
+ bool hasNext;
+ char *tzname = NULL;
- return executeKeyValueMethod(cxt, jsp, jb, found);
+ if (unwrap && JsonbType(jb) == jbvArray)
+ return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
+ false);
- default:
- elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
- }
+ if (JsonItemIsNumber(jb))
+ {
+ bool error = false;
+ float8 unix_epoch;
+ TimestampTz tstz;
- return res;
-}
+ if (JsonItemIsNumeric(jb))
+ {
+ Datum flt = DirectFunctionCall1(numeric_float8_no_overflow,
+ JsonItemNumericDatum(jb));
-/*
- * Unwrap current array item and execute jsonpath for each of its elements.
- */
-static JsonPathExecResult
-executeItemUnwrapTargetArray(JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonbValue *jb, JsonValueList *found,
- bool unwrapElements)
-{
- if (jb->type != jbvBinary)
- {
- Assert(jb->type != jbvArray);
- elog(ERROR, "invalid jsonb array value type: %d", jb->type);
- }
+ unix_epoch = DatumGetFloat8(flt);
+ }
+ else
+ {
+ Assert(JsonItemIsDouble(jb));
+ unix_epoch = JsonItemDouble(jb);
+ }
- return executeAnyItem
- (cxt, jsp, jb->val.binary.data, found, 1, 1, 1,
- false, unwrapElements);
-}
+ tstz = float8_timestamptz_internal(unix_epoch, &error);
-/*
- * Execute next jsonpath item if exists. Otherwise put "v" to the "found"
- * list if provided.
- */
-static JsonPathExecResult
-executeNextItem(JsonPathExecContext *cxt,
- JsonPathItem *cur, JsonPathItem *next,
- JsonbValue *v, JsonValueList *found, bool copy)
-{
- JsonPathItem elem;
- bool hasNext;
+ if (error)
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+ errmsg("UNIX epoch is out ouf timestamptz range"))));
- if (!cur)
- hasNext = next != NULL;
- else if (next)
- hasNext = jspHasNext(cur);
- else
- {
- next = &elem;
- hasNext = jspGetNext(cur, next);
- }
+ value = TimestampTzGetDatum(tstz);
+ typid = TIMESTAMPTZOID;
+ tz = 0;
+ res = jperOk;
- if (hasNext)
- return executeItem(cxt, next, v, found);
+ hasNext = jspGetNext(jsp, &elem);
- if (found)
- JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+ if (!hasNext && !found)
+ break;
- return jperOk;
-}
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
-/*
- * Same as executeItem(), but when "unwrap == true" automatically unwraps
- * each array item from the resulting sequence in lax mode.
- */
-static JsonPathExecResult
-executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonbValue *jb, bool unwrap,
- JsonValueList *found)
-{
- if (unwrap && jspAutoUnwrap(cxt))
- {
- JsonValueList seq = {0};
- JsonValueListIterator it;
- JsonPathExecResult res = executeItem(cxt, jsp, jb, &seq);
- JsonbValue *item;
+ JsonItemInitDatetime(jb, value, typid, typmod, tz);
- if (jperIsError(res))
- return res;
+ res = executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
+ break;
+ }
+ else if (!JsonItemIsString(jb))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+ errmsg("jsonpath item method .%s() can only be applied to a string or number",
+ jspOperationName(jsp->type)))));
- JsonValueListInitIterator(&seq, &it);
- while ((item = JsonValueListNext(&seq, &it)))
- {
- Assert(item->type != jbvArray);
+ datetime = cstring_to_text_with_len(JsonItemString(jb).val,
+ JsonItemString(jb).len);
- if (JsonbType(item) == jbvArray)
- executeItemUnwrapTargetArray(cxt, NULL, item, found, false);
- else
- JsonValueListAppend(found, item);
- }
+ if (jsp->content.args.left)
+ {
+ text *template;
+ char *template_str;
+ int template_len;
- return jperOk;
- }
+ jspGetLeftArg(jsp, &elem);
- return executeItem(cxt, jsp, jb, found);
-}
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
-/*
- * Same as executeItemOptUnwrapResult(), but with error suppression.
- */
-static JsonPathExecResult
-executeItemOptUnwrapResultNoThrow(JsonPathExecContext *cxt,
- JsonPathItem *jsp,
- JsonbValue *jb, bool unwrap,
- JsonValueList *found)
-{
- JsonPathExecResult res;
- bool throwErrors = cxt->throwErrors;
+ template_str = jspGetString(&elem, &template_len);
- cxt->throwErrors = false;
- res = executeItemOptUnwrapResult(cxt, jsp, jb, unwrap, found);
- cxt->throwErrors = throwErrors;
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = {0};
+ JsonPathExecResult tzres;
+ JsonItem *tzjsi;
+
+ jspGetRightArg(jsp, &elem);
+ tzres = executeItem(cxt, &elem, jb, &tzlist);
+ if (jperIsError(tzres))
+ return tzres;
+
+ if (JsonValueListLength(&tzlist) != 1 ||
+ (!JsonItemIsString((tzjsi = JsonValueListHead(&tzlist))) &&
+ !JsonItemIsNumeric(tzjsi)))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+ errmsg("timezone argument of jsonpath item method .%s() is not a singleton string or number",
+ jspOperationName(jsp->type)))));
- return res;
-}
+ if (JsonItemIsString(tzjsi))
+ tzname = pnstrdup(JsonItemString(tzjsi).val,
+ JsonItemString(tzjsi).len);
+ else
+ {
+ bool error = false;
-/* Execute boolean-valued jsonpath expression. */
-static JsonPathBool
-executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonbValue *jb, bool canHaveNext)
-{
- JsonPathItem larg;
- JsonPathItem rarg;
- JsonPathBool res;
- JsonPathBool res2;
+ tz = numeric_int4_opt_error(JsonItemNumeric(tzjsi),
+ &error);
- if (!canHaveNext && jspHasNext(jsp))
- elog(ERROR, "boolean jsonpath item cannot have next item");
+ if (error || tz == PG_INT32_MIN)
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+ errmsg("timezone argument of jsonpath item method .%s() is out of integer range",
+ jspOperationName(jsp->type)))));
- switch (jsp->type)
- {
- case jpiAnd:
- jspGetLeftArg(jsp, &larg);
- res = executeBoolItem(cxt, &larg, jb, false);
+ tz = -tz;
+ }
+ }
- if (res == jpbFalse)
- return jpbFalse;
+ if (template_len)
+ {
+ template = cstring_to_text_with_len(template_str,
+ template_len);
- /*
- * SQL/JSON says that we should check second arg in case of
- * jperError
- */
+ if (tryToParseDatetime(template, datetime, tzname, false,
+ &value, &typid, &typmod,
+ &tz, jspThrowErrors(cxt)))
+ res = jperOk;
+ else
+ res = jperError;
+ }
+ }
- jspGetRightArg(jsp, &rarg);
- res2 = executeBoolItem(cxt, &rarg, jb, false);
+ if (res == jperNotFound)
+ {
+ /* Try to recognize one of ISO formats. */
+ static const char *fmt_str[] =
+ {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+
+ /* cache for format texts */
+ static text *fmt_txt[lengthof(fmt_str)] = {0};
+ int i;
+
+ for (i = 0; i < lengthof(fmt_str); i++)
+ {
+ if (!fmt_txt[i])
+ {
+ MemoryContext oldcxt =
+ MemoryContextSwitchTo(TopMemoryContext);
- return res2 == jpbTrue ? res : res2;
+ fmt_txt[i] = cstring_to_text(fmt_str[i]);
+ MemoryContextSwitchTo(oldcxt);
+ }
- case jpiOr:
- jspGetLeftArg(jsp, &larg);
- res = executeBoolItem(cxt, &larg, jb, false);
+ if (tryToParseDatetime(fmt_txt[i], datetime, tzname,
+ true, &value, &typid, &typmod,
+ &tz, false))
+ {
+ res = jperOk;
+ break;
+ }
+ }
- if (res == jpbTrue)
- return jpbTrue;
+ if (res == jperNotFound)
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+ errmsg("unrecognized datetime format"),
+ errhint("use datetime template argument for explicit format specification"))));
+ }
- jspGetRightArg(jsp, &rarg);
- res2 = executeBoolItem(cxt, &rarg, jb, false);
+ if (tzname)
+ pfree(tzname);
- return res2 == jpbFalse ? res : res2;
+ pfree(datetime);
- case jpiNot:
- jspGetArg(jsp, &larg);
+ if (jperIsError(res))
+ break;
- res = executeBoolItem(cxt, &larg, jb, false);
+ hasNext = jspGetNext(jsp, &elem);
- if (res == jpbUnknown)
- return jpbUnknown;
+ if (!hasNext && !found)
+ break;
- return res == jpbTrue ? jpbFalse : jpbTrue;
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
- case jpiIsUnknown:
- jspGetArg(jsp, &larg);
- res = executeBoolItem(cxt, &larg, jb, false);
- return res == jpbUnknown ? jpbTrue : jpbFalse;
+ JsonItemInitDatetime(jb, value, typid, typmod, tz);
- case jpiEqual:
- case jpiNotEqual:
- case jpiLess:
- case jpiGreater:
- case jpiLessOrEqual:
- case jpiGreaterOrEqual:
- jspGetLeftArg(jsp, &larg);
- jspGetRightArg(jsp, &rarg);
- return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
- executeComparison, NULL);
+ res = executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
- case jpiStartsWith: /* 'whole STARTS WITH initial' */
- jspGetLeftArg(jsp, &larg); /* 'whole' */
- jspGetRightArg(jsp, &rarg); /* 'initial' */
- return executePredicate(cxt, jsp, &larg, &rarg, jb, false,
- executeStartsWith, NULL);
+ case jpiKeyValue:
+ if (unwrap && JsonbType(jb) == jbvArray)
+ return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
- case jpiLikeRegex: /* 'expr LIKE_REGEX pattern FLAGS flags' */
+ return executeKeyValueMethod(cxt, jsp, jb, found);
+
+ case jpiSequence:
{
- /*
- * 'expr' is a sequence-returning expression. 'pattern' is a
- * regex string literal. SQL/JSON standard requires XQuery
- * regexes, but we use Postgres regexes here. 'flags' is a
- * string literal converted to integer flags at compile-time.
- */
- JsonLikeRegexContext lrcxt = {0};
+ JsonPathItem next;
+ bool hasNext = jspGetNext(jsp, &next);
+ JsonValueList list;
+ JsonValueList *plist = hasNext ? &list : found;
+ JsonValueListIterator it;
+ int i;
- jspInitByBuffer(&larg, jsp->base,
- jsp->content.like_regex.expr);
+ for (i = 0; i < jsp->content.sequence.nelems; i++)
+ {
+ JsonItem *v;
- return executePredicate(cxt, jsp, &larg, NULL, jb, false,
- executeLikeRegex, &lrcxt);
- }
+ if (hasNext)
+ memset(&list, 0, sizeof(list));
- case jpiExists:
- jspGetArg(jsp, &larg);
+ jspGetSequenceElement(jsp, i, &elem);
+ res = executeItem(cxt, &elem, jb, plist);
- if (jspStrictAbsenseOfErrors(cxt))
+ if (jperIsError(res))
+ break;
+
+ if (!hasNext)
+ {
+ if (!found && res == jperOk)
+ break;
+ continue;
+ }
+
+ JsonValueListInitIterator(&list, &it);
+
+ while ((v = JsonValueListNext(&list, &it)))
+ {
+ res = executeItem(cxt, &next, v, found);
+
+ if (jperIsError(res) || (!found && res == jperOk))
+ {
+ i = jsp->content.sequence.nelems;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+
+ case jpiArray:
{
- /*
- * In strict mode we must get a complete list of values to
- * check that there are no errors at all.
- */
- JsonValueList vals = {0};
- JsonPathExecResult res =
- executeItemOptUnwrapResultNoThrow(cxt, &larg, jb,
- false, &vals);
+ JsonValueList list = {0};
+ JsonbValue *arr;
+ JsonItem jsi;
- if (jperIsError(res))
- return jpbUnknown;
+ if (jsp->content.arg)
+ {
+ jspGetArg(jsp, &elem);
+ res = executeItem(cxt, &elem, jb, &list);
- return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
+ if (jperIsError(res))
+ break;
+ }
+
+ arr = wrapItemsInArray(&list, cxt->isJsonb);
+
+ res = executeNextItem(cxt, jsp, NULL,
+ JsonbValueToJsonItem(arr, &jsi),
+ found, true);
+ break;
}
- else
+
+ case jpiObject:
{
- JsonPathExecResult res =
- executeItemOptUnwrapResultNoThrow(cxt, &larg, jb,
- false, NULL);
+ JsonbParseState *ps = NULL;
+ JsonbValue *obj;
+ JsonItem jsibuf;
+ int i;
+ JsonBuilderFunc push =
+ cxt->isJsonb ? pushJsonbValue : pushJsonValue;
- if (jperIsError(res))
- return jpbUnknown;
+ push(&ps, WJB_BEGIN_OBJECT, NULL);
- return res == jperOk ? jpbTrue : jpbFalse;
+ for (i = 0; i < jsp->content.object.nfields; i++)
+ {
+ JsonItem *jsi;
+ JsonItem jsitmp;
+ JsonPathItem key;
+ JsonPathItem val;
+ JsonbValue valjbv;
+ JsonValueList key_list = {0};
+ JsonValueList val_list = {0};
+
+ jspGetObjectField(jsp, i, &key, &val);
+
+ res = executeItem(cxt, &key, jb, &key_list);
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&key_list) != 1 ||
+ !(jsi = getScalar(JsonValueListHead(&key_list), jbvString)))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_JSON_SCALAR_REQUIRED),
+ errmsg("key in jsonpath object constructor must be a singleton string")))); /* XXX */
+
+ pushJsonbValue(&ps, WJB_KEY, JsonItemJbv(jsi));
+
+ res = executeItem(cxt, &val, jb, &val_list);
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&val_list) != 1)
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
+ errmsg("value in jsonpath object constructor must be a singleton"))));
+
+ jsi = JsonValueListHead(&val_list);
+ jsi = wrapJsonObjectOrArray(jsi, &jsitmp, cxt->isJsonb);
+
+ push(&ps, WJB_VALUE, JsonItemToJsonbValue(jsi, &valjbv));
+ }
+
+ obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
+ jb = JsonbValueToJsonItem(obj, &jsibuf);
+
+ res = executeNextItem(cxt, jsp, NULL, jb, found, true);
+ break;
}
+ case jpiLambda:
+ elog(ERROR, "jsonpath lambda expression cannot be executed directly");
+ break;
+
+ case jpiMethod:
+ case jpiFunction:
+ res = executeFunction(cxt, jsp, jb, found);
+ break;
+
default:
- elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
- return jpbUnknown;
+ elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
}
+
+ return res;
}
/*
- * Execute nested (filters etc.) boolean expression pushing current SQL/JSON
- * item onto the stack.
+ * Unwrap current array item and execute jsonpath for each of its elements.
*/
-static JsonPathBool
-executeNestedBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonbValue *jb)
+static JsonPathExecResult
+executeItemUnwrapTargetArray(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, JsonValueList *found,
+ bool unwrapElements)
{
- JsonbValue *prev;
- JsonPathBool res;
+ if (JsonItemIsArray(jb))
+ {
+ JsonPathExecResult res = jperNotFound;
+ JsonbValue *elem = JsonItemArray(jb).elems;
+ JsonbValue *last = elem + JsonItemArray(jb).nElems;
- prev = cxt->current;
- cxt->current = jb;
- res = executeBoolItem(cxt, jsp, jb, false);
- cxt->current = prev;
+ for (; elem < last; elem++)
+ {
+ if (jsp)
+ {
+ JsonItem buf;
- return res;
+ res = executeItemOptUnwrapTarget(cxt, jsp,
+ JsonbValueToJsonItem(elem, &buf),
+ found, unwrapElements);
+
+ if (jperIsError(res))
+ break;
+ if (res == jperOk && !found)
+ break;
+ }
+ else
+ {
+ if (found)
+ {
+ JsonItem *jsi = palloc(sizeof(*jsi));
+
+ JsonValueListAppend(found, JsonbValueToJsonItem(elem, jsi));
+ }
+ else
+ return jperOk;
+ }
+ }
+
+ return res;
+ }
+
+ return executeAnyItem
+ (cxt, jsp, JsonItemBinary(jb).data, found, false, 1, 1, 1,
+ false, unwrapElements);
}
/*
- * Implementation of several jsonpath nodes:
- * - jpiAny (.** accessor),
- * - jpiAnyKey (.* accessor),
- * - jpiAnyArray ([*] accessor)
+ * Execute next jsonpath item if exists. Otherwise put "v" to the "found"
+ * list if provided.
*/
static JsonPathExecResult
-executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc,
- JsonValueList *found, uint32 level, uint32 first, uint32 last,
- bool ignoreStructuralErrors, bool unwrapNext)
+executeNextItem(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonItem *v, JsonValueList *found, bool copy)
{
- JsonPathExecResult res = jperNotFound;
- JsonbIterator *it;
- int32 r;
- JsonbValue v;
+ JsonPathItem elem;
+ bool hasNext;
- check_stack_depth();
+ if (!cur)
+ hasNext = next != NULL;
+ else if (next)
+ hasNext = jspHasNext(cur);
+ else
+ {
+ next = &elem;
+ hasNext = jspGetNext(cur, next);
+ }
- if (level > last)
- return res;
+ if (hasNext)
+ return executeItem(cxt, next, v, found);
- it = JsonbIteratorInit(jbc);
+ if (found)
+ JsonValueListAppend(found, copy ? copyJsonItem(v) : v);
- /*
- * Recursively iterate over jsonb objects/arrays
- */
- while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ return jperOk;
+}
+
+/*
+ * Same as executeItem(), but when "unwrap == true" automatically unwraps
+ * each array item from the resulting sequence in lax mode.
+ */
+static JsonPathExecResult
+executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, bool unwrap,
+ JsonValueList *found)
+{
+ if (unwrap && jspAutoUnwrap(cxt))
{
- if (r == WJB_KEY)
- {
- r = JsonbIteratorNext(&it, &v, true);
- Assert(r == WJB_VALUE);
- }
+ JsonValueList seq = {0};
+ JsonValueListIterator it;
+ JsonPathExecResult res = executeItem(cxt, jsp, jb, &seq);
+ JsonItem *item;
+ int count;
- if (r == WJB_VALUE || r == WJB_ELEM)
- {
+ if (jperIsError(res))
+ return res;
- if (level >= first ||
- (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
- v.type != jbvBinary)) /* leaves only requested */
- {
- /* check expression */
- if (jsp)
- {
- if (ignoreStructuralErrors)
- {
- bool savedIgnoreStructuralErrors;
+ count = JsonValueListLength(&seq);
- savedIgnoreStructuralErrors = cxt->ignoreStructuralErrors;
- cxt->ignoreStructuralErrors = true;
- res = executeItemOptUnwrapTarget(cxt, jsp, &v, found, unwrapNext);
- cxt->ignoreStructuralErrors = savedIgnoreStructuralErrors;
- }
- else
- res = executeItemOptUnwrapTarget(cxt, jsp, &v, found, unwrapNext);
+ if (!count)
+ return jperNotFound;
- if (jperIsError(res))
- break;
+ /* Optimize copying of singleton item into empty list */
+ if (count == 1 &&
+ JsonbType((item = JsonValueListHead(&seq))) != jbvArray)
+ {
+ if (JsonValueListIsEmpty(found))
+ *found = seq;
+ else
+ JsonValueListAppend(found, item);
- if (res == jperOk && !found)
- break;
- }
- else if (found)
- JsonValueListAppend(found, copyJsonbValue(&v));
- else
- return jperOk;
- }
+ return jperOk;
+ }
- if (level < last && v.type == jbvBinary)
+ JsonValueListInitIterator(&seq, &it);
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (JsonbType(item) == jbvArray)
+ executeItemUnwrapTargetArray(cxt, NULL, item, found, false);
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ return executeItem(cxt, jsp, jb, found);
+}
+
+typedef struct JsonPathFuncCache
+{
+ FmgrInfo finfo;
+ JsonPathItem *args;
+ void **argscache;
+} JsonPathFuncCache;
+
+static JsonPathFuncCache *
+prepareFunctionCache(JsonPathExecContext *cxt, JsonPathItem *jsp)
+{
+ MemoryContext oldcontext;
+ JsonPathFuncCache *cache = cxt->cache[jsp->content.func.id];
+ List *funcname = list_make1(makeString(jsp->content.func.name));
+ Oid argtypes[] = {JSONPATH_FCXTOID};
+ Oid funcid;
+ int32 i;
+
+ if (cache)
+ return cache;
+
+ funcid = LookupFuncName(funcname,
+ sizeof(argtypes) / sizeof(argtypes[0]), argtypes,
+ false);
+
+ if (get_func_rettype(funcid) != INT8OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("return type of jsonpath item function %s is not %s",
+ NameListToString(funcname), format_type_be(INT8OID))));
+
+ oldcontext = MemoryContextSwitchTo(cxt->cache_mcxt);
+
+ cache = cxt->cache[jsp->content.func.id] = palloc0(sizeof(*cache));
+
+ fmgr_info(funcid, &cache->finfo);
+
+ cache->args = palloc(sizeof(*cache->args) * jsp->content.func.nargs);
+ cache->argscache = palloc0(sizeof(*cache->argscache) *
+ jsp->content.func.nargs);
+
+ for (i = 0; i < jsp->content.func.nargs; i++)
+ jspGetFunctionArg(jsp, i, &cache->args[i]);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return cache;
+}
+
+static JsonPathExecResult
+executeFunction(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb,
+ JsonValueList *result /*, bool needBool */)
+{
+ JsonPathFuncCache *cache = prepareFunctionCache(cxt, jsp);
+ JsonPathFuncContext fcxt;
+ JsonValueList tmpres = {0};
+ JsonValueListIterator tmpiter;
+ JsonPathExecResult res;
+ JsonItem *jsi;
+
+ fcxt.cxt = cxt;
+ fcxt.funcname = jsp->content.func.name;
+ fcxt.jb = jb;
+ fcxt.result = jspHasNext(jsp) ? &tmpres : result;
+ fcxt.args = cache->args;
+ fcxt.argscache = cache->argscache;
+ fcxt.nargs = jsp->content.func.nargs;
+
+ if (jsp->type == jpiMethod)
+ {
+ JsonValueList items = {0};
+ JsonValueListIterator iter;
+
+ /* skip first item argument */
+ fcxt.args++;
+ fcxt.argscache++;
+ fcxt.nargs--;
+
+ res = executeItem(cxt, &cache->args[0], jb, &items);
+
+ if (jperIsError(res))
+ return res;
+
+ JsonValueListInitIterator(&items, &iter);
+
+ while ((jsi = JsonValueListNext(&items, &iter)))
+ {
+ fcxt.item = jsi;
+
+ res = (JsonPathExecResult)
+ DatumGetPointer(FunctionCall2(&cache->finfo,
+ PointerGetDatum(&fcxt),
+ PointerGetDatum(NULL)));
+ if (jperIsError(res))
+ return res;
+ }
+ }
+ else
+ {
+ fcxt.item = NULL;
+
+ res = (JsonPathExecResult)
+ DatumGetPointer(FunctionCall2(&cache->finfo,
+ PointerGetDatum(&fcxt),
+ PointerGetDatum(NULL)));
+ if (jperIsError(res))
+ return res;
+ }
+
+ if (!jspHasNext(jsp))
+ return res;
+
+ JsonValueListInitIterator(&tmpres, &tmpiter);
+
+ while ((jsi = JsonValueListNext(&tmpres, &tmpiter)))
+ {
+ res = executeNextItem(cxt, jsp, NULL, jsi, result, false /*, needBool FIXME */);
+
+ if (jperIsError(res))
+ return res;
+ }
+
+ return res;
+}
+
+/*
+ * Same as executeItemOptUnwrapResult(), but with error suppression.
+ */
+static JsonPathExecResult
+executeItemOptUnwrapResultNoThrow(JsonPathExecContext *cxt,
+ JsonPathItem *jsp,
+ JsonItem *jb, bool unwrap,
+ JsonValueList *found)
+{
+ JsonPathExecResult res;
+ bool throwErrors = cxt->throwErrors;
+
+ cxt->throwErrors = false;
+ res = executeItemOptUnwrapResult(cxt, jsp, jb, unwrap, found);
+ cxt->throwErrors = throwErrors;
+
+ return res;
+}
+
+/* Execute boolean-valued jsonpath expression. */
+static JsonPathBool
+executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, bool canHaveNext)
+{
+ JsonPathItem larg;
+ JsonPathItem rarg;
+ JsonPathBool res;
+ JsonPathBool res2;
+
+ if (!canHaveNext && jspHasNext(jsp))
+ elog(ERROR, "boolean jsonpath item cannot have next item");
+
+ switch (jsp->type)
+ {
+ case jpiAnd:
+ jspGetLeftArg(jsp, &larg);
+ res = executeBoolItem(cxt, &larg, jb, false);
+
+ if (res == jpbFalse)
+ return jpbFalse;
+
+ /*
+ * SQL/JSON says that we should check second arg in case of
+ * jperError
+ */
+
+ jspGetRightArg(jsp, &rarg);
+ res2 = executeBoolItem(cxt, &rarg, jb, false);
+
+ return res2 == jpbTrue ? res : res2;
+
+ case jpiOr:
+ jspGetLeftArg(jsp, &larg);
+ res = executeBoolItem(cxt, &larg, jb, false);
+
+ if (res == jpbTrue)
+ return jpbTrue;
+
+ jspGetRightArg(jsp, &rarg);
+ res2 = executeBoolItem(cxt, &rarg, jb, false);
+
+ return res2 == jpbFalse ? res : res2;
+
+ case jpiNot:
+ jspGetArg(jsp, &larg);
+
+ res = executeBoolItem(cxt, &larg, jb, false);
+
+ if (res == jpbUnknown)
+ return jpbUnknown;
+
+ return res == jpbTrue ? jpbFalse : jpbTrue;
+
+ case jpiIsUnknown:
+ jspGetArg(jsp, &larg);
+ res = executeBoolItem(cxt, &larg, jb, false);
+ return res == jpbUnknown ? jpbTrue : jpbFalse;
+
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ jspGetLeftArg(jsp, &larg);
+ jspGetRightArg(jsp, &rarg);
+ return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
+ executeComparison, NULL);
+
+ case jpiStartsWith: /* 'whole STARTS WITH initial' */
+ jspGetLeftArg(jsp, &larg); /* 'whole' */
+ jspGetRightArg(jsp, &rarg); /* 'initial' */
+ return executePredicate(cxt, jsp, &larg, &rarg, jb, false,
+ executeStartsWith, NULL);
+
+ case jpiLikeRegex: /* 'expr LIKE_REGEX pattern FLAGS flags' */
+ {
+ /*
+ * 'expr' is a sequence-returning expression. 'pattern' is a
+ * regex string literal. SQL/JSON standard requires XQuery
+ * regexes, but we use Postgres regexes here. 'flags' is a
+ * string literal converted to integer flags at compile-time.
+ */
+ JsonLikeRegexContext lrcxt = {0};
+
+ jspInitByBuffer(&larg, jsp->base,
+ jsp->content.like_regex.expr);
+
+ return executePredicate(cxt, jsp, &larg, NULL, jb, false,
+ executeLikeRegex, &lrcxt);
+ }
+
+ case jpiExists:
+ jspGetArg(jsp, &larg);
+
+ if (jspStrictAbsenseOfErrors(cxt))
+ {
+ /*
+ * In strict mode we must get a complete list of values to
+ * check that there are no errors at all.
+ */
+ JsonValueList vals = {0};
+ JsonPathExecResult res =
+ executeItemOptUnwrapResultNoThrow(cxt, &larg, jb,
+ false, &vals);
+
+ if (jperIsError(res))
+ return jpbUnknown;
+
+ return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
+ }
+ else
+ {
+ JsonPathExecResult res =
+ executeItemOptUnwrapResultNoThrow(cxt, &larg, jb,
+ false, NULL);
+
+ if (jperIsError(res))
+ return jpbUnknown;
+
+ return res == jperOk ? jpbTrue : jpbFalse;
+ }
+
+ default:
+ elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
+ return jpbUnknown;
+ }
+}
+
+/*
+ * Execute nested expression pushing current SQL/JSON item onto the stack.
+ */
+static inline JsonPathExecResult
+executeItemNested(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, JsonValueList *found)
+{
+ JsonItemStackEntry current;
+ JsonPathExecResult res;
+
+ pushJsonItem(&cxt->stack, ¤t, jb, &cxt->baseObject);
+ res = executeItem(cxt, jsp, jb, found);
+ popJsonItem(&cxt->stack);
+
+ return res;
+}
+
+JsonPathExecResult
+jspExecuteItemNested(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, JsonValueList *found)
+{
+ return executeItemNested(cxt, jsp, jb, found);
+}
+
+/*
+ * Execute nested (filters etc.) boolean expression pushing current SQL/JSON
+ * item onto the stack.
+ */
+static JsonPathBool
+executeNestedBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb)
+{
+ JsonItemStackEntry current;
+ JsonPathBool res;
+
+ pushJsonItem(&cxt->stack, ¤t, jb, &cxt->baseObject);
+ res = executeBoolItem(cxt, jsp, jb, false);
+ popJsonItem(&cxt->stack);
+
+ return res;
+}
+
+static int
+getLambdaVar(void *cxt, bool isJsonb, char *varName, int varNameLen,
+ JsonItem *val, JsonbValue *baseObject)
+{
+ JsonLambdaVars *vars = cxt;
+ int i = 0;
+
+ for (i = 0; i < vars->nvars; i++)
+ {
+ JsonLambdaVar *var = &vars->vars[i];
+
+ if (!strncmp(var->name, varName, varNameLen))
+ {
+ *val = *var->val;
+ return 0; /* FIXME */
+ }
+ }
+
+ return vars->parentGetVar(vars->parentVars, isJsonb, varName, varNameLen,
+ val, baseObject);
+}
+
+static JsonPathExecResult
+recursiveExecuteLambdaVars(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, JsonValueList *found,
+ JsonItem **params, int nparams, void **pcache)
+{
+ JsonLambdaCache *cache = *pcache;
+ JsonPathExecResult res;
+ int i;
+
+ if (nparams > 0 && !cache)
+ {
+ MemoryContext oldcontext = MemoryContextSwitchTo(cxt->cache_mcxt);
+
+ cache = *pcache = palloc0(sizeof(*cache));
+ cache->vars.vars = palloc(sizeof(*cache->vars.vars) * nparams);
+ cache->vars.nvars = nparams;
+ cache->vars.parentVars = NULL;
+ cache->vars.parentGetVar = NULL;
+
+ for (i = 0; i < nparams; i++)
+ {
+ JsonLambdaVar *var = &cache->vars.vars[i];
+ char varname[20];
+
+ snprintf(varname, sizeof(varname), "%d", i + 1);
+
+ var->name = pstrdup(varname);
+ var->val = NULL;
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ for (i = 0; i < nparams; i++)
+ cache->vars.vars[i].val = params[i];
+
+ cache->vars.parentVars = cxt->vars;
+ cache->vars.parentGetVar = cxt->getVar;
+ cxt->vars = cache;
+ cxt->getVar = getLambdaVar;
+
+ if (found)
+ {
+ res = executeItem(cxt, jsp, jb, found);
+ }
+ else
+ {
+ JsonPathBool ok = executeBoolItem(cxt, jsp, jb, false);
+
+ if (ok == jpbUnknown)
+ {
+ cxt->vars = cache->vars.parentVars;
+ cxt->getVar = cache->vars.parentGetVar;
+
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NO_JSON_ITEM),
+ errmsg(ERRMSG_NO_JSON_ITEM)))); /* FIXME */
+ }
+ res = ok == jpbTrue ? jperOk : jperNotFound;
+ }
+
+ cxt->vars = cache->vars.parentVars;
+ cxt->getVar = cache->vars.parentGetVar;
+
+ return res;
+}
+
+static inline JsonPathExecResult
+recursiveExecuteLambdaExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, JsonValueList *found,
+ JsonItem **params, int nparams, void **pcache)
+{
+ JsonPathItem expr;
+ JsonPathExecResult res;
+ JsonLambdaCache *cache = *pcache;
+ JsonLambdaArg *oldargs;
+ int i;
+
+ if (jsp->content.lambda.nparams > nparams)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("jsonpath lambda arguments mismatch: expected %d but given %d",
+ jsp->content.lambda.nparams, nparams)));
+
+ if (jsp->content.lambda.nparams > 0 && !cache)
+ {
+ MemoryContext oldcontext = MemoryContextSwitchTo(cxt->cache_mcxt);
+
+ cache = *pcache = palloc0(sizeof(*cache));
+ cache->args = palloc(sizeof(cache->args[0]) *
+ jsp->content.lambda.nparams);
+
+ for (i = 0; i < jsp->content.lambda.nparams; i++)
+ {
+ JsonLambdaArg *arg = &cache->args[i];
+ JsonPathItem argname;
+
+ jspGetLambdaParam(jsp, i, &argname);
+
+ if (argname.type != jpiArgument)
+ elog(ERROR, "invalid jsonpath lambda argument item type: %d",
+ argname.type);
+
+ arg->name = jspGetString(&argname, &arg->namelen);
+ arg->val = NULL;
+ arg->next = arg + 1;
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ oldargs = cxt->args;
+
+ if (jsp->content.lambda.nparams > 0)
+ {
+ for (i = 0; i < jsp->content.lambda.nparams; i++)
+ cache->args[i].val = params[i];
+
+ cache->args[jsp->content.lambda.nparams - 1].next = oldargs;
+ cxt->args = &cache->args[0];
+ }
+
+ jspGetLambdaExpr(jsp, &expr);
+
+ /* found == NULL is used here when executing boolean filter expressions */
+ if (found)
+ {
+ res = executeItem(cxt, &expr, jb, found);
+ }
+ else
+ {
+ JsonPathBool ok = executeBoolItem(cxt, &expr, jb, false);
+
+ if (ok == jpbUnknown)
+ {
+ cxt->args = oldargs;
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NO_JSON_ITEM),
+ errmsg(ERRMSG_NO_JSON_ITEM)))); /* FIXME */
+ }
+
+ res = ok == jpbTrue ? jperOk : jperNotFound;
+ }
+
+ cxt->args = oldargs;
+
+ return res;
+}
+
+JsonPathExecResult
+jspExecuteLambda(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, JsonValueList *res,
+ JsonItem **params, int nparams, void **cache)
+{
+ return jsp->type == jpiLambda
+ ? recursiveExecuteLambdaExpr(cxt, jsp, jb, res, params, nparams, cache)
+ : recursiveExecuteLambdaVars(cxt, jsp, jb, res, params, nparams, cache);
+}
+
+/*
+ * Implementation of several jsonpath nodes:
+ * - jpiAny (.** accessor),
+ * - jpiAnyKey (.* accessor),
+ * - jpiAnyArray ([*] accessor)
+ */
+static JsonPathExecResult
+executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc,
+ JsonValueList *found, bool outPath, uint32 level, uint32 first,
+ uint32 last, bool ignoreStructuralErrors, bool unwrapNext)
+{
+ JsonPathExecResult res = jperNotFound;
+ JsonxIterator it;
+ int32 r;
+ JsonItem v;
+ char *keyStr = NULL;
+ int keyLen = 0;
+ JsonValueList items = {0};
+ JsonValueList *pitems = found;
+ bool isObject;
+
+ check_stack_depth();
+
+ if (level > last)
+ return res;
+
+ if (pitems && outPath)
+ pitems = &items;
+
+ isObject = JsonContainerIsObject(jbc);
+
+ JsonxIteratorInit(&it, jbc, cxt->isJsonb);
+
+ /*
+ * Recursively iterate over jsonb objects/arrays
+ */
+ while ((r = JsonxIteratorNext(&it, JsonItemJbv(&v), true)) != WJB_DONE)
+ {
+ if (r == WJB_KEY)
+ {
+ keyStr = JsonItemString(&v).val;
+ keyLen = JsonItemString(&v).len;
+ r = JsonxIteratorNext(&it, JsonItemJbv(&v), true);
+ Assert(r == WJB_VALUE);
+
+ if (pitems == &items)
+ JsonValueListClear(pitems);
+ }
+
+ if (r == WJB_VALUE || r == WJB_ELEM)
+ {
+
+ if (level >= first ||
+ (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
+ !JsonItemIsBinary(&v))) /* leaves only requested */
+ {
+ /* check expression */
+ if (jsp)
+ {
+ if (ignoreStructuralErrors)
+ {
+ bool savedIgnoreStructuralErrors;
+
+ savedIgnoreStructuralErrors = cxt->ignoreStructuralErrors;
+ cxt->ignoreStructuralErrors = true;
+ res = executeItemOptUnwrapTarget(cxt, jsp, &v, pitems, unwrapNext);
+ cxt->ignoreStructuralErrors = savedIgnoreStructuralErrors;
+ }
+ else
+ res = executeItemOptUnwrapTarget(cxt, jsp, &v, pitems, unwrapNext);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ else if (found)
+ JsonValueListAppend(pitems, copyJsonItem(&v));
+ else
+ return jperOk;
+ }
+
+ if (level < last && JsonItemIsBinary(&v))
+ {
+ res = executeAnyItem
+ (cxt, jsp, JsonItemBinary(&v).data, pitems, outPath,
+ level + 1, first, last,
+ ignoreStructuralErrors, unwrapNext);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && found == NULL)
+ break;
+ }
+ }
+
+ if (isObject && !JsonValueListIsEmpty(&items) && !jperIsError(res))
+ JsonValueListConcat(found, prependKey(keyStr, keyLen,
+ &items, cxt->isJsonb));
+ }
+
+ if (!isObject && !JsonValueListIsEmpty(&items) && !jperIsError(res))
+ appendWrappedItems(found, &items, cxt->isJsonb);
+
+ return res;
+}
+
+/*
+ * Execute unary or binary predicate.
+ *
+ * Predicates have existence semantics, because their operands are item
+ * sequences. Pairs of items from the left and right operand's sequences are
+ * checked. TRUE returned only if any pair satisfying the condition is found.
+ * In strict mode, even if the desired pair has already been found, all pairs
+ * still need to be examined to check the absence of errors. If any error
+ * occurs, UNKNOWN (analogous to SQL NULL) is returned.
+ */
+static JsonPathBool
+executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred,
+ JsonPathItem *larg, JsonPathItem *rarg, JsonItem *jb,
+ bool unwrapRightArg, JsonPathPredicateCallback exec,
+ void *param)
+{
+ JsonPathExecResult res;
+ JsonValueListIterator lseqit;
+ JsonValueList lseq = {0};
+ JsonValueList rseq = {0};
+ JsonItem *lval;
+ bool error = false;
+ bool found = false;
+
+ /* Left argument is always auto-unwrapped. */
+ res = executeItemOptUnwrapResultNoThrow(cxt, larg, jb, true, &lseq);
+ if (jperIsError(res))
+ return jpbUnknown;
+
+ if (rarg)
+ {
+ /* Right argument is conditionally auto-unwrapped. */
+ res = executeItemOptUnwrapResultNoThrow(cxt, rarg, jb,
+ unwrapRightArg, &rseq);
+ if (jperIsError(res))
+ return jpbUnknown;
+ }
+
+ JsonValueListInitIterator(&lseq, &lseqit);
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit;
+ JsonItem *rval;
+ bool first = true;
+
+ JsonValueListInitIterator(&rseq, &rseqit);
+ if (rarg)
+ rval = JsonValueListNext(&rseq, &rseqit);
+ else
+ rval = NULL;
+
+ /* Loop over right arg sequence or do single pass otherwise */
+ while (rarg ? (rval != NULL) : first)
+ {
+ JsonPathBool res = exec(pred, lval, rval, param);
+
+ if (res == jpbUnknown)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ else if (res == jpbTrue)
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+
+ first = false;
+ if (rarg)
+ rval = JsonValueListNext(&rseq, &rseqit);
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+static float8
+float8_mod_error(float8 val1, float8 val2, bool *error)
+{
+ float8 res;
+
+ if (error)
+ {
+ res = float8_div_error(val1, val2, error);
+ if (!*error)
+ {
+ res = float8_mul_error(trunc(res), val1, error);
+ if (!*error)
+ res = float8_mi_error(val1, res, error);
+ }
+ }
+ else
+ {
+ res = float8_div(val1, val2);
+ res = float8_mul(trunc(res), val1);
+ res = float8_mi(val1, res);
+ }
+
+ return res;
+}
+
+/*
+ * Execute binary arithmetic expression on singleton numeric operands.
+ * Array operands are automatically unwrapped in lax mode.
+ */
+static JsonPathExecResult
+executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, BinaryNumericFunc numFunc,
+ BinaryDoubleFunc dblFunc, JsonValueList *found)
+{
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = {0};
+ JsonValueList rseq = {0};
+ JsonItem *lval;
+ JsonItem *rval;
+ Numeric res;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /*
+ * XXX: By standard only operands of multiplicative expressions are
+ * unwrapped. We extend it to other binary arithmetic expressions too.
+ */
+ jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &lseq);
+ if (jperIsError(jper))
+ return jper;
+
+ jspGetRightArg(jsp, &elem);
+
+ jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &rseq);
+ if (jperIsError(jper))
+ return jper;
+
+ if (JsonValueListLength(&lseq) != 1 ||
+ !(lval = getNumber(JsonValueListHead(&lseq))))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
+ errmsg("left operand of jsonpath operator %s is not a single numeric value",
+ jspOperationName(jsp->type)))));
+
+ if (JsonValueListLength(&rseq) != 1 ||
+ !(rval = getNumber(JsonValueListHead(&rseq))))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
+ errmsg("right operand of jsonpath operator %s is not a single numeric value",
+ jspOperationName(jsp->type)))));
+
+ if (JsonItemIsDouble(lval) && JsonItemIsDouble(rval))
+ {
+ float8 ld = JsonItemDouble(lval);
+ float8 rd = JsonItemDouble(rval);
+ float8 r;
+
+ if (jspThrowErrors(cxt))
+ {
+ r = dblFunc(ld, rd, NULL);
+ }
+ else
+ {
+ bool error = false;
+
+ r = dblFunc(ld, rd, &error);
+
+ if (error)
+ return jperError;
+ }
+
+ if (!jspGetNext(jsp, &elem) && !found)
+ return jperOk;
+
+ lval = palloc(sizeof(*lval));
+ JsonItemInitDouble(lval, r);
+
+ return executeNextItem(cxt, jsp, &elem, lval, found, false);
+ }
+ else if (JsonItemIsDouble(lval))
+ {
+ if (!convertJsonDoubleToNumeric(lval, lval))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert infinity to numeric"))));
+ }
+ else if (JsonItemIsDouble(rval))
+ {
+ if (!convertJsonDoubleToNumeric(rval, rval))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert infinity to numeric"))));
+ }
+
+ if (jspThrowErrors(cxt))
+ {
+ res = numFunc(JsonItemNumeric(lval), JsonItemNumeric(rval), NULL);
+ }
+ else
+ {
+ bool error = false;
+
+ res = numFunc(JsonItemNumeric(lval), JsonItemNumeric(rval), &error);
+
+ if (error)
+ return jperError;
+ }
+
+ if (!jspGetNext(jsp, &elem) && !found)
+ return jperOk;
+
+ lval = palloc(sizeof(*lval));
+ JsonItemInitNumeric(lval, res);
+
+ return executeNextItem(cxt, jsp, &elem, lval, found, false);
+}
+
+/*
+ * Execute unary arithmetic expression for each numeric item in its operand's
+ * sequence. Array operand is automatically unwrapped in lax mode.
+ */
+static JsonPathExecResult
+executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, PGFunction numFunc, PGFunction dblFunc,
+ JsonValueList *found)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = {0};
+ JsonValueListIterator it;
+ JsonItem *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &seq);
+
+ if (jperIsError(jper))
+ return jper;
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ JsonValueListInitIterator(&seq, &it);
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if ((val = getNumber(val)))
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else
+ {
+ if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_JSON_NUMBER_NOT_FOUND),
+ errmsg("operand of unary jsonpath operator %s is not a numeric value",
+ jspOperationName(jsp->type)))));
+ }
+
+ if (JsonItemIsNumeric(val))
+ {
+ if (numFunc)
+ JsonItemNumeric(val) =
+ DatumGetNumeric(DirectFunctionCall1(numFunc,
+ JsonItemNumericDatum(val)));
+ }
+ else
+ {
+ Assert(JsonItemIsDouble(val));
+ if (dblFunc)
+ JsonItemDouble(val) =
+ DatumGetFloat8(DirectFunctionCall1(dblFunc,
+ JsonItemDoubleDatum(val)));
+ }
+
+ jper2 = executeNextItem(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * STARTS_WITH predicate callback.
+ *
+ * Check if the 'whole' string starts from 'initial' string.
+ */
+static JsonPathBool
+executeStartsWith(JsonPathItem *jsp, JsonItem *whole, JsonItem *initial,
+ void *param)
+{
+ if (!(whole = getScalar(whole, jbvString)))
+ return jpbUnknown; /* error */
+
+ if (!(initial = getScalar(initial, jbvString)))
+ return jpbUnknown; /* error */
+
+ if (JsonItemString(whole).len >= JsonItemString(initial).len &&
+ !memcmp(JsonItemString(whole).val,
+ JsonItemString(initial).val,
+ JsonItemString(initial).len))
+ return jpbTrue;
+
+ return jpbFalse;
+}
+
+/*
+ * LIKE_REGEX predicate callback.
+ *
+ * Check if the string matches regex pattern.
+ */
+static JsonPathBool
+executeLikeRegex(JsonPathItem *jsp, JsonItem *str, JsonItem *rarg,
+ void *param)
+{
+ JsonLikeRegexContext *cxt = param;
+
+ if (!(str = getScalar(str, jbvString)))
+ return jpbUnknown;
+
+ /* Cache regex text and converted flags. */
+ if (!cxt->regex)
+ {
+ uint32 flags = jsp->content.like_regex.flags;
+
+ cxt->regex =
+ cstring_to_text_with_len(jsp->content.like_regex.pattern,
+ jsp->content.like_regex.patternlen);
+
+ /* Convert regex flags. */
+ cxt->cflags = REG_ADVANCED;
+
+ if (flags & JSP_REGEX_ICASE)
+ cxt->cflags |= REG_ICASE;
+ if (flags & JSP_REGEX_MLINE)
+ cxt->cflags |= REG_NEWLINE;
+ if (flags & JSP_REGEX_SLINE)
+ cxt->cflags &= ~REG_NEWLINE;
+ if (flags & JSP_REGEX_WSPACE)
+ cxt->cflags |= REG_EXPANDED;
+ if ((flags & JSP_REGEX_QUOTE) &&
+ !(flags & (JSP_REGEX_MLINE | JSP_REGEX_SLINE | JSP_REGEX_WSPACE)))
+ {
+ cxt->cflags &= ~REG_ADVANCED;
+ cxt->cflags |= REG_QUOTE;
+ }
+ }
+
+ if (RE_compile_and_execute(cxt->regex, JsonItemString(str).val,
+ JsonItemString(str).len,
+ cxt->cflags, DEFAULT_COLLATION_OID, 0, NULL))
+ return jpbTrue;
+
+ return jpbFalse;
+}
+
+/*
+ * Execute numeric item methods (.abs(), .floor(), .ceil()) using the specified
+ * user function 'func'.
+ */
+static JsonPathExecResult
+executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, bool unwrap, PGFunction numericFunc,
+ PGFunction doubleFunc, JsonValueList *found)
+{
+ JsonPathItem next;
+ JsonItem res;
+ Datum datum;
+
+ if (unwrap && JsonbType(jb) == jbvArray)
+ return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+
+ if (!(jb = getNumber(jb)))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
+ errmsg("jsonpath item method .%s() can only be applied to a numeric value",
+ jspOperationName(jsp->type)))));
+
+ if (JsonItemIsNumeric(jb))
+ {
+ datum = DirectFunctionCall1(numericFunc, JsonItemNumericDatum(jb));
+ }
+ else
+ {
+ Assert(JsonItemIsDouble(jb));
+ datum = DirectFunctionCall1(doubleFunc, JsonItemDoubleDatum(jb));
+ }
+
+ if (!jspGetNext(jsp, &next) && !found)
+ return jperOk;
+
+ if (JsonItemIsNumeric(jb))
+ JsonItemInitNumericDatum(&res, datum);
+ else
+ JsonItemInitDouble(&res, DatumGetFloat8(datum));
+
+ return executeNextItem(cxt, jsp, &next, &res, found, true);
+}
+
+/*
+ * Implementation of .keyvalue() method.
+ *
+ * .keyvalue() method returns a sequence of object's key-value pairs in the
+ * following format: '{ "key": key, "value": value, "id": id }'.
+ *
+ * "id" field is an object identifier which is constructed from the two parts:
+ * base object id and its binary offset in base object's jsonb:
+ * id = 10000000000 * base_object_id + obj_offset_in_base_object
+ *
+ * 10000000000 (10^10) -- is a first round decimal number greater than 2^32
+ * (maximal offset in jsonb). Decimal multiplier is used here to improve the
+ * readability of identifiers.
+ *
+ * Base object is usually a root object of the path: context item '$' or path
+ * variable '$var', literals can't produce objects for now. But if the path
+ * contains generated objects (.keyvalue() itself, for example), then they
+ * become base object for the subsequent .keyvalue().
+ *
+ * Id of '$' is 0. Id of '$var' is its ordinal (positive) number in the list
+ * of variables (see getJsonPathVariable()). Ids for generated objects
+ * are assigned using global counter JsonPathExecContext.lastGeneratedObjectId.
+ */
+static JsonPathExecResult
+executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonItem *jb, JsonValueList *found)
+{
+ JsonPathExecResult res = jperNotFound;
+ JsonPathItem next;
+ JsonbContainer *jbc;
+ JsonItem bin;
+ JsonbValue key;
+ JsonbValue val;
+ JsonbValue idval;
+ JsonbValue keystr;
+ JsonbValue valstr;
+ JsonbValue idstr;
+ JsonxIterator it;
+ JsonbIteratorToken tok;
+ JsonBuilderFunc push;
+ int64 id;
+ bool hasNext;
+
+ if (JsonbType(jb) != jbvObject)
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_JSON_OBJECT_NOT_FOUND),
+ errmsg("jsonpath item method .%s() can only be applied to an object",
+ jspOperationName(jsp->type)))));
+
+ jb = wrapJsonObjectOrArray(jb, &bin, cxt->isJsonb);
+ jbc = JsonItemBinary(jb).data;
+
+ if (!JsonContainerSize(jbc))
+ return jperNotFound; /* no key-value pairs */
+
+ hasNext = jspGetNext(jsp, &next);
+
+ keystr.type = jbvString;
+ keystr.val.string.val = "key";
+ keystr.val.string.len = 3;
+
+ valstr.type = jbvString;
+ valstr.val.string.val = "value";
+ valstr.val.string.len = 5;
+
+ idstr.type = jbvString;
+ idstr.val.string.val = "id";
+ idstr.val.string.len = 2;
+
+ /* construct object id from its base object and offset inside that */
+ id = cxt->isJsonb ?
+ (int64) ((char *)(JsonContainer *) jbc -
+ (char *)(JsonContainer *) cxt->baseObject.jbc) :
+ (int64) (((JsonContainer *) jbc)->data -
+ ((JsonContainer *) cxt->baseObject.jbc)->data);
+
+ id += (int64) cxt->baseObject.id * INT64CONST(10000000000);
+
+ idval.type = jbvNumeric;
+ idval.val.numeric = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+ Int64GetDatum(id)));
+
+ push = cxt->isJsonb ? pushJsonbValue : pushJsonValue;
+
+ JsonxIteratorInit(&it, jbc, cxt->isJsonb);
+
+ while ((tok = JsonxIteratorNext(&it, &key, true)) != WJB_DONE)
+ {
+ JsonBaseObjectInfo baseObject;
+ JsonItem obj;
+ JsonbParseState *ps;
+ JsonbValue *keyval;
+ Jsonx *jsonx;
+
+ if (tok != WJB_KEY)
+ continue;
+
+ res = jperOk;
+
+ if (!hasNext && !found)
+ break;
+
+ tok = JsonxIteratorNext(&it, &val, true);
+ Assert(tok == WJB_VALUE);
+
+ ps = NULL;
+ push(&ps, WJB_BEGIN_OBJECT, NULL);
+
+ pushJsonbValue(&ps, WJB_KEY, &keystr);
+ pushJsonbValue(&ps, WJB_VALUE, &key);
+
+ pushJsonbValue(&ps, WJB_KEY, &valstr);
+ push(&ps, WJB_VALUE, &val);
+
+ pushJsonbValue(&ps, WJB_KEY, &idstr);
+ pushJsonbValue(&ps, WJB_VALUE, &idval);
+
+ keyval = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
+
+ jsonx = JsonbValueToJsonx(keyval, cxt->isJsonb);
+
+ if (cxt->isJsonb)
+ JsonbInitBinary(JsonItemJbv(&obj), &jsonx->jb);
+ else
+ JsonInitBinary(JsonItemJbv(&obj), &jsonx->js);
+
+ baseObject = setBaseObject(cxt, &obj, cxt->lastGeneratedObjectId++);
+
+ res = executeNextItem(cxt, jsp, &next, &obj, found, true);
+
+ cxt->baseObject = baseObject;
+
+ if (jperIsError(res))
+ return res;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ return res;
+}
+
+/*
+ * Convert boolean execution status 'res' to a boolean JSON item and execute
+ * next jsonpath.
+ */
+static JsonPathExecResult
+appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonValueList *found, JsonPathBool res)
+{
+ JsonPathItem next;
+ JsonItem jsi;
+
+ if (!jspGetNext(jsp, &next) && !found)
+ return jperOk; /* found singleton boolean value */
+
+ if (res == jpbUnknown)
+ JsonItemInitNull(&jsi);
+ else
+ JsonItemInitBool(&jsi, res == jpbTrue);
+
+ return executeNextItem(cxt, jsp, &next, &jsi, found, true);
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value.
+ *
+ * If node is a variable then its id returned, otherwise 0 returned.
+ */
+static void
+getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
+ JsonItem *value)
+{
+ switch (item->type)
+ {
+ case jpiNull:
+ JsonItemInitNull(value);
+ break;
+ case jpiBool:
+ JsonItemInitBool(value, jspGetBool(item));
+ break;
+ case jpiNumeric:
+ JsonItemInitNumeric(value, jspGetNumeric(item));
+ break;
+ case jpiString:
+ {
+ int len;
+ char *str = jspGetString(item, &len);
+
+ JsonItemInitString(value, str, len);
+ break;
+ }
+ case jpiVariable:
+ getJsonPathVariable(cxt, item, value);
+ return;
+
+ case jpiArgument:
+ {
+ JsonLambdaArg *arg;
+ char *argname;
+ int argnamelen;
+
+ argname = jspGetString(item, &argnamelen);
+
+ for (arg = cxt->args; arg; arg = arg->next)
+ {
+ if (arg->namelen == argnamelen &&
+ !strncmp(arg->name, argname, argnamelen))
+ {
+ *value = *arg->val;
+ return; /* FIXME object id */
+ }
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("cannot find jsonpath lambda variable '%s'",
+ pnstrdup(argname, argnamelen))));
+ break;
+ }
+
+ default:
+ elog(ERROR, "unexpected jsonpath item type");
+ }
+}
+
+/*
+ * Get the value of variable passed to jsonpath executor
+ */
+static void
+getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable,
+ JsonItem *value)
+{
+ char *varName;
+ int varNameLength;
+ JsonItem baseObject;
+ int baseObjectId;
+
+ Assert(variable->type == jpiVariable);
+ varName = jspGetString(variable, &varNameLength);
+
+ if (!cxt->vars ||
+ (baseObjectId = cxt->getVar(cxt->vars, cxt->isJsonb,
+ varName, varNameLength,
+ value, JsonItemJbv(&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
+getJsonPathVariableFromJsonx(void *varsJsonx, bool isJsonb,
+ char *varName, int varNameLength,
+ JsonItem *value, JsonbValue *baseObject)
+{
+ Jsonx *vars = varsJsonx;
+
+ if (!varName)
+ {
+ if (vars &&
+ !(isJsonb ?
+ JsonContainerIsObject(&vars->jb.root) :
+ JsonContainerIsObject(&vars->js.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 */
+ }
+
+ if (!vars)
+ return -1;
+
+ if (isJsonb)
+ {
+ Jsonb *jb = &vars->jb;
+
+ if (!jsonbFindKeyInObject(&jb->root, varName, varNameLength,
+ JsonItemJbv(value)))
+ return -1;
+
+ JsonbInitBinary(baseObject, jb);
+ }
+ else
+ {
+ Json *js = &vars->js;
+
+ if (!jsonFindLastKeyInObject(&js->root, varName, varNameLength,
+ JsonItemJbv(value)))
+ return -1;
+
+ JsonInitBinary(baseObject, js);
+ }
+
+ return 1;
+}
+
+/**************** Support functions for JsonPath execution *****************/
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+int
+JsonxArraySize(JsonItem *jb, bool isJsonb)
+{
+ if (JsonItemIsArray(jb))
+ return JsonItemArray(jb).nElems;
+
+ if (JsonItemIsBinary(jb))
+ {
+ if (isJsonb)
+ {
+ JsonbContainer *jbc = JsonItemBinary(jb).data;
+
+ if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc))
+ return JsonContainerSize(jbc);
+ }
+ else
+ {
+ JsonContainer *jc = (JsonContainer *) JsonItemBinary(jb).data;
+
+ if (JsonContainerIsArray(jc) && !JsonContainerIsScalar(jc))
+ return JsonTextContainerSize(jc);
+ }
+ }
+
+ return -1;
+}
+
+/* Comparison predicate callback. */
+static JsonPathBool
+executeComparison(JsonPathItem *cmp, JsonItem *lv, JsonItem *rv, void *p)
+{
+ return jspCompareItems(cmp->type, lv, rv);
+}
+
+/*
+ * Compare two SQL/JSON items using comparison operation 'op'.
+ */
+JsonPathBool
+jspCompareItems(int32 op, JsonItem *jsi1, JsonItem *jsi2)
+{
+ JsonbValue *jb1 = JsonItemJbv(jsi1);
+ JsonbValue *jb2 = JsonItemJbv(jsi2);
+ JsonItem jsibuf;
+ int cmp;
+ bool res;
+
+ if (JsonItemGetType(jsi1) != JsonItemGetType(jsi2))
+ {
+ if (JsonItemIsNull(jsi1) || JsonItemIsNull(jsi2))
+
+ /*
+ * Equality and order comparison of nulls to non-nulls returns
+ * always false, but inequality comparison returns true.
+ */
+ return op == jpiNotEqual ? jpbTrue : jpbFalse;
+
+ if (!JsonItemIsNumber(jsi1) || !JsonItemIsNumber(jsi2))
+ /* Non-null items of different not-number types are not comparable. */
+ return jpbUnknown;
+
+ if (JsonItemIsDouble(jsi1))
+ {
+ if (!convertJsonDoubleToNumeric(jsi1, &jsibuf))
+ return jpbUnknown;
+ jsi1 = &jsibuf;
+ jb1 = JsonItemJbv(jsi1);
+ }
+ else if (JsonItemIsDouble(jsi2))
+ {
+ if (!convertJsonDoubleToNumeric(jsi2, &jsibuf))
+ return jpbUnknown;
+ jsi2 = &jsibuf;
+ jb2 = JsonItemJbv(jsi2);
+ }
+ }
+
+ switch (JsonItemGetType(jsi1))
+ {
+ case jbvNull:
+ cmp = 0;
+ break;
+ case jbvBool:
+ cmp = jb1->val.boolean == jb2->val.boolean ? 0 :
+ jb1->val.boolean ? 1 : -1;
+ break;
+ case jbvNumeric:
+ cmp = compareNumeric(jb1->val.numeric, jb2->val.numeric);
+ break;
+ case jsiDouble:
+ cmp = float8_cmp_internal(JsonItemDouble(jsi1),
+ JsonItemDouble(jsi2));
+ break;
+ case jbvString:
+ if (op == jpiEqual)
+ return jb1->val.string.len != jb2->val.string.len ||
+ memcmp(jb1->val.string.val,
+ jb2->val.string.val,
+ jb1->val.string.len) ? jpbFalse : jpbTrue;
+
+ cmp = varstr_cmp(jb1->val.string.val, jb1->val.string.len,
+ jb2->val.string.val, jb2->val.string.len,
+ DEFAULT_COLLATION_OID);
+ break;
+ case jsiDatetime:
+ {
+ bool error = false;
+
+ cmp = compareDatetime(JsonItemDatetime(jsi1).value,
+ JsonItemDatetime(jsi1).typid,
+ JsonItemDatetime(jsi1).tz,
+ JsonItemDatetime(jsi2).value,
+ JsonItemDatetime(jsi2).typid,
+ JsonItemDatetime(jsi2).tz,
+ &error);
+
+ if (error)
+ return jpbUnknown;
+ }
+ break;
+
+ case jbvBinary:
+ case jbvArray:
+ case jbvObject:
+ return jpbUnknown; /* non-scalars are not comparable */
+
+ default:
+ elog(ERROR, "invalid jsonb value type %d", JsonItemGetType(jsi1));
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "unrecognized jsonpath operation: %d", op);
+ return jpbUnknown;
+ }
+
+ return res ? jpbTrue : jpbFalse;
+}
+
+/* Compare two numerics */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
+ NumericGetDatum(a),
+ NumericGetDatum(b)));
+}
+
+JsonItem *
+copyJsonItem(JsonItem *src)
+{
+ JsonItem *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+JsonItem *
+JsonbValueToJsonItem(JsonbValue *jbv, JsonItem *jsi)
+{
+ *JsonItemJbv(jsi) = *jbv;
+ return jsi;
+}
+
+static JsonbValue *
+JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv)
+{
+ switch (JsonItemGetType(jsi))
+ {
+ case jsiDatetime:
+ jbv->type = jbvString;
+ jbv->val.string.val = JsonEncodeDateTime(NULL,
+ JsonItemDatetime(jsi).value,
+ JsonItemDatetime(jsi).typid,
+ &JsonItemDatetime(jsi).tz);
+ jbv->val.string.len = strlen(jbv->val.string.val);
+ return jbv;
+
+ case jsiDouble:
+ {
+ float8 val = JsonItemDouble(jsi);
+
+ if (isinf(val)) /* numeric can be NaN but not Inf */
+ {
+ jbv->type = jbvString;
+ jbv->val.string.val = float8out_internal(val);
+ jbv->val.string.len = strlen(jbv->val.string.val);
+ }
+ else
+ {
+ jbv->type = jbvNumeric;
+ jbv->val.numeric =
+ DatumGetNumeric(DirectFunctionCall1(float8_numeric,
+ Float8GetDatum(val)));
+ }
+ return jbv;
+ }
+
+ default:
+ return JsonItemJbv(jsi);
+ }
+}
+
+Jsonb *
+JsonItemToJsonb(JsonItem *jsi)
+{
+ JsonbValue jbv;
+
+ return JsonbValueToJsonb(JsonItemToJsonbValue(jsi, &jbv));
+}
+
+static const char *
+JsonItemTypeName(JsonItem *jsi)
+{
+ switch (JsonItemGetType(jsi))
+ {
+ case jsiDatetime:
+ switch (JsonItemDatetime(jsi).typid)
+ {
+ case DATEOID:
+ return "date";
+ case TIMEOID:
+ return "time without time zone";
+ case TIMETZOID:
+ return "time with time zone";
+ case TIMESTAMPOID:
+ return "timestamp without time zone";
+ case TIMESTAMPTZOID:
+ return "timestamp with time zone";
+ default:
+ elog(ERROR, "unrecognized jsonb value datetime type: %d",
+ JsonItemDatetime(jsi).typid);
+ return "unknown";
+ }
+
+ default:
+ return JsonbTypeName(JsonItemJbv(jsi));
+ }
+}
+
+/*
+ * Execute array subscript expression and convert resulting numeric item to
+ * the integer type with truncation.
+ */
+static JsonPathExecResult
+getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb,
+ int32 *index)
+{
+ JsonItem *jbv;
+ JsonValueList found = {0};
+ JsonPathExecResult res = executeItem(cxt, jsp, jb, &found);
+ Datum numeric_index;
+ bool have_error = false;
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1 ||
+ !(jbv = getNumber(JsonValueListHead(&found))))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
+ errmsg("jsonpath array subscript is not a single numeric value"))));
+
+ if (JsonItemIsNumeric(jbv))
+ {
+ numeric_index = DirectFunctionCall2(numeric_trunc,
+ JsonItemNumericDatum(jbv),
+ Int32GetDatum(0));
+
+ *index = numeric_int4_opt_error(DatumGetNumeric(numeric_index),
+ &have_error);
+ }
+ else
+ {
+ float8 val;
+
+ Assert(JsonItemIsDouble(jbv));
+
+ val = floor(JsonItemDouble(jbv));
+
+ if (unlikely(val < (float8) PG_INT32_MIN ||
+ val >= -((float8) PG_INT32_MIN) ||
+ isnan(val)))
+ have_error = true;
+ else
+ *index = (int32) val;
+ }
+
+ if (have_error)
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
+ errmsg("jsonpath array subscript is out of integer range"))));
+
+ return jperOk;
+}
+
+/* Save base object and its id needed for the execution of .keyvalue(). */
+static JsonBaseObjectInfo
+setBaseObject(JsonPathExecContext *cxt, JsonItem *jbv, int32 id)
+{
+ JsonBaseObjectInfo baseObject = cxt->baseObject;
+
+ cxt->baseObject.jbc = !JsonItemIsBinary(jbv) ? NULL :
+ (JsonbContainer *) JsonItemBinary(jbv).data;
+ cxt->baseObject.id = id;
+
+ return baseObject;
+}
+
+void
+JsonValueListClear(JsonValueList *jvl)
+{
+ jvl->head = NULL;
+ jvl->tail = NULL;
+ jvl->length = 0;
+}
+
+void
+JsonValueListAppend(JsonValueList *jvl, JsonItem *jsi)
+{
+ jsi->next = NULL;
+
+ if (jvl->tail)
+ {
+ jvl->tail->next = jsi;
+ jvl->tail = jsi;
+ }
+ else
+ {
+ Assert(!jvl->head);
+ jvl->head = jvl->tail = jsi;
+ }
+
+ jvl->length++;
+}
+
+void
+JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2)
+{
+ if (!jvl1->tail)
+ {
+ Assert(!jvl1->head);
+ Assert(!jvl1->length);
+ *jvl1 = jvl2;
+ }
+ else if (jvl2.head)
+ {
+ Assert(jvl1->head);
+ jvl1->tail->next = jvl2.head;
+ jvl1->tail = jvl2.tail;
+ jvl1->length += jvl2.length;
+ }
+}
+
+List *
+JsonValueListGetList(JsonValueList *jvl)
+{
+ List *list = NIL;
+ JsonItem *jsi;
+
+ for (jsi = jvl->head; jsi; jsi = jsi->next)
+ list = lappend(list, jsi);
+
+ return list;
+}
+
+void
+JsonValueListInitIterator(const JsonValueList *jvl, JsonValueListIterator *it)
+{
+ it->next = jvl->head;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+JsonItem *
+JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it)
+{
+ JsonItem *result = it->next;
+
+ if (result)
+ it->next = result->next;
+
+ return result;
+}
+
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+static JsonbValue *
+JsonbInitBinary(JsonbValue *jbv, Jsonb *jb)
+{
+ jbv->type = jbvBinary;
+ jbv->val.binary.data = &jb->root;
+ jbv->val.binary.len = VARSIZE_ANY_EXHDR(jb);
+
+ return jbv;
+}
+
+/*
+ * Initialize a binary JsonbValue with the given json container.
+ */
+static inline JsonbValue *
+JsonInitBinary(JsonbValue *jbv, Json *js)
+{
+ jbv->type = jbvBinary;
+ jbv->val.binary.data = (void *) &js->root;
+ jbv->val.binary.len = js->root.len;
+
+ return jbv;
+}
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+static JsonItem *
+JsonxWrapInBinary(JsonItem *jsi, JsonItem *out, bool isJsonb)
+{
+ if (!out)
+ out = palloc(sizeof(*out));
+
+ if (isJsonb)
+ {
+ Jsonb *jb = JsonItemToJsonb(jsi);
+
+ JsonbInitBinary(JsonItemJbv(out), jb);
+ }
+ else
+ {
+ Json *js = JsonItemToJson(jsi);
+
+ JsonInitBinary(JsonItemJbv(out), js);
+ }
+
+ return out;
+}
+
+static JsonItem *
+wrapJsonObjectOrArray(JsonItem *js, JsonItem *buf, bool isJsonb)
+{
+ if (!JsonItemIsObject(js) && !JsonItemIsArray(js))
+ return js;
+
+ return JsonxWrapInBinary(js, buf, isJsonb);
+}
+
+/*
+ * Returns jbv* type of of JsonbValue. Note, it never returns jbvBinary as is.
+ */
+int
+JsonbType(JsonItem *jb)
+{
+ int type = JsonItemGetType(jb);
+
+ if (type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) JsonItemBinary(jb).data;
+
+ /* Scalars should be always extracted during jsonpath execution. */
+ Assert(!JsonContainerIsScalar(jbc));
+
+ if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "invalid jsonb container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+/*
+ * Convert jsonb to a C-string stripping quotes from scalar strings.
+ */
+static char *
+JsonbValueUnquote(JsonbValue *jbv, int *len, bool isJsonb)
+{
+ switch (jbv->type)
+ {
+ case jbvString:
+ *len = jbv->val.string.len;
+ return jbv->val.string.val;
+
+ case jbvBool:
+ *len = jbv->val.boolean ? 4 : 5;
+ return jbv->val.boolean ? "true" : "false";
+
+ case jbvNumeric:
+ *len = -1;
+ return DatumGetCString(DirectFunctionCall1(numeric_out,
+ PointerGetDatum(jbv->val.numeric)));
+
+ case jbvNull:
+ *len = 4;
+ return "null";
+
+ case jbvBinary:
{
- res = executeAnyItem
- (cxt, jsp, v.val.binary.data, found,
- level + 1, first, last,
- ignoreStructuralErrors, unwrapNext);
+ JsonbValue jbvbuf;
+
+ if (isJsonb ?
+ JsonbExtractScalar(jbv->val.binary.data, &jbvbuf) :
+ JsonExtractScalar((JsonContainer *) jbv->val.binary.data, &jbvbuf))
+ return JsonbValueUnquote(&jbvbuf, len, isJsonb);
+
+ *len = -1;
+ return isJsonb ?
+ JsonbToCString(NULL, jbv->val.binary.data, jbv->val.binary.len) :
+ JsonToCString(NULL, (JsonContainer *) jbv->val.binary.data, jbv->val.binary.len);
+ }
+
+ default:
+ elog(ERROR, "unexpected jsonb value type: %d", jbv->type);
+ return NULL;
+ }
+}
+
+static char *
+JsonItemUnquote(JsonItem *jsi, int *len, bool isJsonb)
+{
+ switch (JsonItemGetType(jsi))
+ {
+ case jsiDatetime:
+ *len = -1;
+ return JsonEncodeDateTime(NULL,
+ JsonItemDatetime(jsi).value,
+ JsonItemDatetime(jsi).typid,
+ &JsonItemDatetime(jsi).tz);
+ case jsiDouble:
+ *len = -1;
+ return float8out_internal(JsonItemDouble(jsi));
+
+ default:
+ return JsonbValueUnquote(JsonItemJbv(jsi), len, isJsonb);
+ }
+}
+
+static text *
+JsonItemUnquoteText(JsonItem *jsi, bool isJsonb)
+{
+ int len;
+ char *str = JsonItemUnquote(jsi, &len, isJsonb);
+
+ if (len < 0)
+ return cstring_to_text(str);
+ else
+ return cstring_to_text_with_len(str, len);
+}
+
+static JsonItem *
+getJsonObjectKey(JsonItem *jsi, char *keystr, int keylen, bool isJsonb,
+ JsonItem *res)
+{
+ JsonbContainer *jbc = JsonItemBinary(jsi).data;
+ JsonbValue *val = isJsonb ?
+ jsonbFindKeyInObject(jbc, keystr, keylen, JsonItemJbv(res)) :
+ jsonFindLastKeyInObject((JsonContainer *) jbc, keystr, keylen,
+ JsonItemJbv(res));
+
+ return val ? res : NULL;
+}
+
+static JsonItem *
+getJsonArrayElement(JsonItem *jb, uint32 index, bool isJsonb, JsonItem *res)
+{
+ JsonbContainer *jbc = JsonItemBinary(jb).data;
+ JsonbValue *elem = isJsonb ?
+ getIthJsonbValueFromContainer(jbc, index, JsonItemJbv(res)) :
+ getIthJsonValueFromContainer((JsonContainer *) jbc, index,
+ JsonItemJbv(res));
+
+ return elem ? res : NULL;
+}
+
+void
+JsonxIteratorInit(JsonxIterator *it, JsonxContainer *jxc, bool isJsonb)
+{
+ it->isJsonb = isJsonb;
+ if (isJsonb)
+ it->it.jb = JsonbIteratorInit((JsonbContainer *) jxc);
+ else
+ it->it.js = JsonIteratorInit((JsonContainer *) jxc);
+}
+
+JsonbIteratorToken
+JsonxIteratorNext(JsonxIterator *it, JsonbValue *jbv, bool skipNested)
+{
+ return it->isJsonb ?
+ JsonbIteratorNext(&it->it.jb, jbv, skipNested) :
+ JsonIteratorNext(&it->it.js, jbv, skipNested);
+}
+
+Json *
+JsonItemToJson(JsonItem *jsi)
+{
+ JsonbValue jbv;
+
+ return JsonbValueToJson(JsonItemToJsonbValue(jsi, &jbv));
+}
+
+static Jsonx *
+JsonbValueToJsonx(JsonbValue *jbv, bool isJsonb)
+{
+ return isJsonb ?
+ (Jsonx *) JsonbValueToJsonb(jbv) :
+ (Jsonx *) JsonbValueToJson(jbv);
+}
+
+Datum
+JsonbValueToJsonxDatum(JsonbValue *jbv, bool isJsonb)
+{
+ return isJsonb ?
+ JsonbPGetDatum(JsonbValueToJsonb(jbv)) :
+ JsonPGetDatum(JsonbValueToJson(jbv));
+}
+
+Datum
+JsonItemToJsonxDatum(JsonItem *jsi, bool isJsonb)
+{
+ JsonbValue jbv;
+
+ return JsonbValueToJsonxDatum(JsonItemToJsonbValue(jsi, &jbv), isJsonb);
+}
+
+/* Get scalar of given type or NULL on type mismatch */
+static JsonItem *
+getScalar(JsonItem *scalar, enum jbvType type)
+{
+ /* Scalars should be always extracted during jsonpath execution. */
+ Assert(!JsonItemIsBinary(scalar) ||
+ !JsonContainerIsScalar(JsonItemBinary(scalar).data));
+
+ return JsonItemGetType(scalar) == type ? scalar : NULL;
+}
+
+static JsonItem *
+getNumber(JsonItem *scalar)
+{
+ /* Scalars should be always extracted during jsonpath execution. */
+ Assert(!JsonItemIsBinary(scalar) ||
+ !JsonContainerIsScalar(JsonItemBinary(scalar).data));
+
+ return JsonItemIsNumber(scalar) ? scalar : NULL;
+}
+
+bool
+convertJsonDoubleToNumeric(JsonItem *dbl, JsonItem *num)
+{
+ if (isinf(JsonItemDouble(dbl)))
+ return false;
+
+ JsonItemInitNumericDatum(num, DirectFunctionCall1(float8_numeric,
+ JsonItemDoubleDatum(dbl)));
+ return true;
+}
+
+/*
+ * Wrap a non-array SQL/JSON item into an array for applying array subscription
+ * path steps in lax mode.
+ */
+JsonItem *
+JsonWrapItemInArray(JsonItem *jsi, bool isJsonb)
+{
+ JsonbParseState *ps = NULL;
+ JsonItem jsibuf;
+ JsonbValue jbvbuf;
+
+ switch (JsonbType(jsi))
+ {
+ case jbvArray:
+ /* Simply return an array item. */
+ return jsi;
+
+ case jbvObject:
+ /*
+ * Need to wrap object into a binary JsonbValue for its unpacking
+ * in pushJsonbValue().
+ */
+ if (!JsonItemIsBinary(jsi))
+ jsi = JsonxWrapInBinary(jsi, &jsibuf, isJsonb);
+ break;
+
+ default:
+ /* Ordinary scalars can be pushed directly. */
+ break;
+ }
+
+ pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL);
+ (isJsonb ? pushJsonbValue : pushJsonValue)(&ps, WJB_ELEM,
+ JsonItemToJsonbValue(jsi, &jbvbuf));
+ jsi = JsonbValueToJsonItem(pushJsonbValue(&ps, WJB_END_ARRAY, NULL), &jsibuf);
+
+ return JsonxWrapInBinary(jsi, NULL, isJsonb);
+}
+
+/* Construct a JSON array from the item list */
+static JsonbValue *
+wrapItemsInArray(const JsonValueList *items, bool isJsonb)
+{
+ JsonbParseState *ps = NULL;
+ JsonValueListIterator it;
+ JsonItem *jsi;
+ JsonBuilderFunc push = isJsonb ? pushJsonbValue : pushJsonValue;
- if (jperIsError(res))
- break;
+ push(&ps, WJB_BEGIN_ARRAY, NULL);
- if (res == jperOk && found == NULL)
- break;
- }
- }
+ JsonValueListInitIterator(items, &it);
+
+ while ((jsi = JsonValueListNext(items, &it)))
+ {
+ JsonItem bin;
+ JsonbValue jbv;
+
+ jsi = wrapJsonObjectOrArray(jsi, &bin, isJsonb);
+ push(&ps, WJB_ELEM, JsonItemToJsonbValue(jsi, &jbv));
}
- return res;
+ return push(&ps, WJB_END_ARRAY, NULL);
}
-/*
- * Execute unary or binary predicate.
- *
- * Predicates have existence semantics, because their operands are item
- * sequences. Pairs of items from the left and right operand's sequences are
- * checked. TRUE returned only if any pair satisfying the condition is found.
- * In strict mode, even if the desired pair has already been found, all pairs
- * still need to be examined to check the absence of errors. If any error
- * occurs, UNKNOWN (analogous to SQL NULL) is returned.
- */
-static JsonPathBool
-executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred,
- JsonPathItem *larg, JsonPathItem *rarg, JsonbValue *jb,
- bool unwrapRightArg, JsonPathPredicateCallback exec,
- void *param)
+JsonbValue *
+JsonWrapItemsInArray(const JsonValueList *items, bool isJsonb)
{
- JsonPathExecResult res;
- JsonValueListIterator lseqit;
- JsonValueList lseq = {0};
- JsonValueList rseq = {0};
- JsonbValue *lval;
- bool error = false;
- bool found = false;
+ return wrapItemsInArray(items, isJsonb);
+}
- /* Left argument is always auto-unwrapped. */
- res = executeItemOptUnwrapResultNoThrow(cxt, larg, jb, true, &lseq);
- if (jperIsError(res))
- return jpbUnknown;
+static void
+appendWrappedItems(JsonValueList *found, JsonValueList *items, bool isJsonb)
+{
+ JsonbValue *wrapped = wrapItemsInArray(items, isJsonb);
+ JsonItem *jsi = palloc(sizeof(*jsi));
- if (rarg)
- {
- /* Right argument is conditionally auto-unwrapped. */
- res = executeItemOptUnwrapResultNoThrow(cxt, rarg, jb,
- unwrapRightArg, &rseq);
- if (jperIsError(res))
- return jpbUnknown;
- }
+ JsonValueListAppend(found, JsonbValueToJsonItem(wrapped, jsi));
+}
- JsonValueListInitIterator(&lseq, &lseqit);
- while ((lval = JsonValueListNext(&lseq, &lseqit)))
- {
- JsonValueListIterator rseqit;
- JsonbValue *rval;
- bool first = true;
+void
+JsonAppendWrappedItems(JsonValueList *found, JsonValueList *items, bool isJsonb)
+{
+ return appendWrappedItems(found, items, isJsonb);
+}
- JsonValueListInitIterator(&rseq, &rseqit);
- if (rarg)
- rval = JsonValueListNext(&rseq, &rseqit);
- else
- rval = NULL;
+static JsonValueList
+prependKey(char *keystr, int keylen, const JsonValueList *items, bool isJsonb)
+{
+ JsonValueList objs = {0};
+ JsonValueListIterator it;
+ JsonItem *val;
+ JsonbValue key;
- /* Loop over right arg sequence or do single pass otherwise */
- while (rarg ? (rval != NULL) : first)
- {
- JsonPathBool res = exec(pred, lval, rval, param);
+ key.type = jbvString;
+ key.val.string.val = keystr;
+ key.val.string.len = keylen;
- if (res == jpbUnknown)
- {
- if (jspStrictAbsenseOfErrors(cxt))
- return jpbUnknown;
+ JsonValueListInitIterator(items, &it);
- error = true;
- }
- else if (res == jpbTrue)
- {
- if (!jspStrictAbsenseOfErrors(cxt))
- return jpbTrue;
+ while ((val = JsonValueListNext(items, &it)))
+ {
+ JsonbValue *obj;
+ JsonbValue valbuf;
+ JsonItem bin;
+ JsonItem *jsi = palloc(sizeof(*jsi));
+ JsonbParseState *ps = NULL;
- found = true;
- }
+ if (JsonItemIsObject(val) || JsonItemIsArray(val))
+ val = JsonxWrapInBinary(val, &bin, isJsonb);
- first = false;
- if (rarg)
- rval = JsonValueListNext(&rseq, &rseqit);
- }
+ pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL);
+ pushJsonbValue(&ps, WJB_KEY, &key);
+ pushJsonbValue(&ps, WJB_VALUE, JsonItemToJsonbValue(val, &valbuf));
+ obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
+
+ JsonValueListAppend(&objs, JsonbValueToJsonItem(obj, jsi));
}
- if (found) /* possible only in strict mode */
- return jpbTrue;
+ return objs;
+}
- if (error) /* possible only in lax mode */
- return jpbUnknown;
+void
+pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, JsonItem *item,
+ JsonBaseObjectInfo *base)
+{
+ entry->item = item;
+ entry->base = *base;
+ entry->parent = *stack;
+ *stack = entry;
+}
- return jpbFalse;
+void
+popJsonItem(JsonItemStack *stack)
+{
+ *stack = (*stack)->parent;
}
-/*
- * Execute binary arithmetic expression on singleton numeric operands.
- * Array operands are automatically unwrapped in lax mode.
- */
-static JsonPathExecResult
-executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonbValue *jb, BinaryArithmFunc func,
- JsonValueList *found)
+static inline Datum
+time_to_timetz(Datum time, int tz, bool *error)
{
- JsonPathExecResult jper;
- JsonPathItem elem;
- JsonValueList lseq = {0};
- JsonValueList rseq = {0};
- JsonbValue *lval;
- JsonbValue *rval;
- Numeric res;
+ TimeADT tm = DatumGetTimeADT(time);
+ TimeTzADT *result = palloc(sizeof(TimeTzADT));
- jspGetLeftArg(jsp, &elem);
+ if (tz == PG_INT32_MIN)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
- /*
- * XXX: By standard only operands of multiplicative expressions are
- * unwrapped. We extend it to other binary arithmetic expressions too.
- */
- jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &lseq);
- if (jperIsError(jper))
- return jper;
+ result->time = tm;
+ result->zone = tz;
- jspGetRightArg(jsp, &elem);
+ return TimeTzADTPGetDatum(result);
+}
- jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &rseq);
- if (jperIsError(jper))
- return jper;
+static inline Datum
+date_to_timestamp(Datum date, bool *error)
+{
+ DateADT dt = DatumGetDateADT(date);
+ Timestamp ts = date2timestamp_internal(dt, error);
- if (JsonValueListLength(&lseq) != 1 ||
- !(lval = getScalar(JsonValueListHead(&lseq), jbvNumeric)))
- RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
- errmsg("left operand of jsonpath operator %s is not a single numeric value",
- jspOperationName(jsp->type)))));
+ return TimestampGetDatum(ts);
+}
- if (JsonValueListLength(&rseq) != 1 ||
- !(rval = getScalar(JsonValueListHead(&rseq), jbvNumeric)))
- RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
- errmsg("right operand of jsonpath operator %s is not a single numeric value",
- jspOperationName(jsp->type)))));
+static inline Datum
+date_to_timestamptz(Datum date, int tz, bool *error)
+{
+ DateADT dt = DatumGetDateADT(date);
+ TimestampTz ts;
- if (jspThrowErrors(cxt))
+ if (tz == PG_INT32_MIN)
{
- res = func(lval->val.numeric, rval->val.numeric, NULL);
+ *error = true;
+ return (Datum) 0;
}
- else
- {
- bool error = false;
- res = func(lval->val.numeric, rval->val.numeric, &error);
+ ts = date2timestamptz_internal(dt, &tz, error);
- if (error)
- return jperError;
- }
+ return TimestampTzGetDatum(ts);
+}
- if (!jspGetNext(jsp, &elem) && !found)
- return jperOk;
+static inline Datum
+timestamp_to_timestamptz(Datum val, int tz, bool *error)
+{
+ Timestamp ts = DatumGetTimestamp(val);
+ TimestampTz tstz;
- lval = palloc(sizeof(*lval));
- lval->type = jbvNumeric;
- lval->val.numeric = res;
+ if (tz == PG_INT32_MIN)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
- return executeNextItem(cxt, jsp, &elem, lval, found, false);
+ tstz = timestamp2timestamptz_internal(ts, &tz, error);
+
+ return TimestampTzGetDatum(tstz);
}
/*
- * Execute unary arithmetic expression for each numeric item in its operand's
- * sequence. Array operand is automatically unwrapped in lax mode.
+ * Cross-type comparison of two datetime SQL/JSON items. If items are
+ * uncomparable, 'error' flag is set.
*/
-static JsonPathExecResult
-executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonbValue *jb, PGFunction func, JsonValueList *found)
+static int
+compareDatetime(Datum val1, Oid typid1, int tz1,
+ Datum val2, Oid typid2, int tz2,
+ bool *error)
{
- JsonPathExecResult jper;
- JsonPathExecResult jper2;
- JsonPathItem elem;
- JsonValueList seq = {0};
- JsonValueListIterator it;
- JsonbValue *val;
- bool hasNext;
+ PGFunction cmpfunc = NULL;
- jspGetArg(jsp, &elem);
- jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &seq);
+ switch (typid1)
+ {
+ case DATEOID:
+ switch (typid2)
+ {
+ case DATEOID:
+ cmpfunc = date_cmp;
- if (jperIsError(jper))
- return jper;
+ break;
- jper = jperNotFound;
+ case TIMESTAMPOID:
+ val1 = date_to_timestamp(val1, error);
+ cmpfunc = timestamp_cmp;
- hasNext = jspGetNext(jsp, &elem);
+ break;
- JsonValueListInitIterator(&seq, &it);
- while ((val = JsonValueListNext(&seq, &it)))
- {
- if ((val = getScalar(val, jbvNumeric)))
- {
- if (!found && !hasNext)
- return jperOk;
- }
- else
- {
- if (!found && !hasNext)
- continue; /* skip non-numerics processing */
+ case TIMESTAMPTZOID:
+ val1 = date_to_timestamptz(val1, tz1, error);
+ cmpfunc = timestamp_cmp;
- RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_JSON_NUMBER_NOT_FOUND),
- errmsg("operand of unary jsonpath operator %s is not a numeric value",
- jspOperationName(jsp->type)))));
- }
+ break;
- if (func)
- val->val.numeric =
- DatumGetNumeric(DirectFunctionCall1(func,
- NumericGetDatum(val->val.numeric)));
+ case TIMEOID:
+ case TIMETZOID:
+ *error = true;
+ return 0;
+ }
+ break;
- jper2 = executeNextItem(cxt, jsp, &elem, val, found, false);
+ case TIMEOID:
+ switch (typid2)
+ {
+ case TIMEOID:
+ cmpfunc = time_cmp;
- if (jperIsError(jper2))
- return jper2;
+ break;
- if (jper2 == jperOk)
- {
- if (!found)
- return jperOk;
- jper = jperOk;
- }
- }
+ case TIMETZOID:
+ val1 = time_to_timetz(val1, tz1, error);
+ cmpfunc = timetz_cmp;
- return jper;
-}
+ break;
-/*
- * STARTS_WITH predicate callback.
- *
- * Check if the 'whole' string starts from 'initial' string.
- */
-static JsonPathBool
-executeStartsWith(JsonPathItem *jsp, JsonbValue *whole, JsonbValue *initial,
- void *param)
-{
- if (!(whole = getScalar(whole, jbvString)))
- return jpbUnknown; /* error */
+ case DATEOID:
+ case TIMESTAMPOID:
+ case TIMESTAMPTZOID:
+ *error = true;
+ return 0;
+ }
+ break;
- if (!(initial = getScalar(initial, jbvString)))
- return jpbUnknown; /* error */
+ case TIMETZOID:
+ switch (typid2)
+ {
+ case TIMEOID:
+ val2 = time_to_timetz(val2, tz2, error);
+ cmpfunc = timetz_cmp;
- if (whole->val.string.len >= initial->val.string.len &&
- !memcmp(whole->val.string.val,
- initial->val.string.val,
- initial->val.string.len))
- return jpbTrue;
+ break;
- return jpbFalse;
-}
+ case TIMETZOID:
+ cmpfunc = timetz_cmp;
-/*
- * LIKE_REGEX predicate callback.
- *
- * Check if the string matches regex pattern.
- */
-static JsonPathBool
-executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg,
- void *param)
-{
- JsonLikeRegexContext *cxt = param;
+ break;
- if (!(str = getScalar(str, jbvString)))
- return jpbUnknown;
+ case DATEOID:
+ case TIMESTAMPOID:
+ case TIMESTAMPTZOID:
+ *error = true;
+ return 0;
+ }
+ break;
+
+ case TIMESTAMPOID:
+ switch (typid2)
+ {
+ case DATEOID:
+ val2 = date_to_timestamp(val2, error);
+ cmpfunc = timestamp_cmp;
+
+ break;
+
+ case TIMESTAMPOID:
+ cmpfunc = timestamp_cmp;
+
+ break;
+
+ case TIMESTAMPTZOID:
+ val1 = timestamp_to_timestamptz(val1, tz1, error);
+ cmpfunc = timestamp_cmp;
+
+ break;
+
+ case TIMEOID:
+ case TIMETZOID:
+ *error = true;
+ return 0;
+ }
+ break;
+
+ case TIMESTAMPTZOID:
+ switch (typid2)
+ {
+ case DATEOID:
+ val2 = date_to_timestamptz(val2, tz2, error);
+ cmpfunc = timestamp_cmp;
+
+ break;
+
+ case TIMESTAMPOID:
+ val2 = timestamp_to_timestamptz(val2, tz2, error);
+ cmpfunc = timestamp_cmp;
- /* Cache regex text and converted flags. */
- if (!cxt->regex)
- {
- uint32 flags = jsp->content.like_regex.flags;
+ break;
- cxt->regex =
- cstring_to_text_with_len(jsp->content.like_regex.pattern,
- jsp->content.like_regex.patternlen);
+ case TIMESTAMPTZOID:
+ cmpfunc = timestamp_cmp;
- /* Convert regex flags. */
- cxt->cflags = REG_ADVANCED;
+ break;
- if (flags & JSP_REGEX_ICASE)
- cxt->cflags |= REG_ICASE;
- if (flags & JSP_REGEX_MLINE)
- cxt->cflags |= REG_NEWLINE;
- if (flags & JSP_REGEX_SLINE)
- cxt->cflags &= ~REG_NEWLINE;
- if (flags & JSP_REGEX_WSPACE)
- cxt->cflags |= REG_EXPANDED;
+ case TIMEOID:
+ case TIMETZOID:
+ *error = true;
+ return 0;
+ }
+ break;
+
+ default:
+ elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+ typid1);
}
- if (RE_compile_and_execute(cxt->regex, str->val.string.val,
- str->val.string.len,
- cxt->cflags, DEFAULT_COLLATION_OID, 0, NULL))
- return jpbTrue;
+ if (*error)
+ return 0;
- return jpbFalse;
+ if (!cmpfunc)
+ elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+ typid2);
+
+ *error = false;
+
+ return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
}
/*
- * Execute numeric item methods (.abs(), .floor(), .ceil()) using the specified
- * user function 'func'.
+ * Try to parse datetime text with the specified datetime template and
+ * default time-zone 'tzname'.
+ * Returns 'value' datum, its type 'typid' and 'typmod'.
+ * Datetime error is rethrown with SQL/JSON errcode if 'throwErrors' is true.
*/
-static JsonPathExecResult
-executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonbValue *jb, bool unwrap, PGFunction func,
- JsonValueList *found)
+static bool
+tryToParseDatetime(text *fmt, text *datetime, char *tzname, bool strict,
+ Datum *value, Oid *typid, int32 *typmod, int *tzp,
+ bool throwErrors)
{
- JsonPathItem next;
- Datum datum;
+ bool error = false;
+ int tz = *tzp;
- if (unwrap && JsonbType(jb) == jbvArray)
- return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+ *value = parse_datetime(datetime, fmt, tzname, strict, typid, typmod,
+ &tz, throwErrors ? NULL : &error);
- if (!(jb = getScalar(jb, jbvNumeric)))
- RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
- errmsg("jsonpath item method .%s() can only be applied to a numeric value",
- jspOperationName(jsp->type)))));
+ if (!error)
+ *tzp = tz;
- datum = DirectFunctionCall1(func, NumericGetDatum(jb->val.numeric));
+ return !error;
+}
- if (!jspGetNext(jsp, &next) && !found)
- return jperOk;
+static void
+JsonItemInitNull(JsonItem *item)
+{
+ item->val.type = jbvNull;
+}
- jb = palloc(sizeof(*jb));
- jb->type = jbvNumeric;
- jb->val.numeric = DatumGetNumeric(datum);
+static void
+JsonItemInitBool(JsonItem *item, bool val)
+{
+ item->val.type = jbvBool;
+ JsonItemBool(item) = val;
+}
- return executeNextItem(cxt, jsp, &next, jb, found, false);
+static void
+JsonItemInitNumeric(JsonItem *item, Numeric val)
+{
+ item->val.type = jbvNumeric;
+ JsonItemNumeric(item) = val;
}
-/*
- * Implementation of .keyvalue() method.
- *
- * .keyvalue() method returns a sequence of object's key-value pairs in the
- * following format: '{ "key": key, "value": value, "id": id }'.
- *
- * "id" field is an object identifier which is constructed from the two parts:
- * base object id and its binary offset in base object's jsonb:
- * id = 10000000000 * base_object_id + obj_offset_in_base_object
- *
- * 10000000000 (10^10) -- is a first round decimal number greater than 2^32
- * (maximal offset in jsonb). Decimal multiplier is used here to improve the
- * readability of identifiers.
- *
- * Base object is usually a root object of the path: context item '$' or path
- * variable '$var', literals can't produce objects for now. But if the path
- * contains generated objects (.keyvalue() itself, for example), then they
- * become base object for the subsequent .keyvalue().
- *
- * Id of '$' is 0. Id of '$var' is its ordinal (positive) number in the list
- * of variables (see getJsonPathVariable()). Ids for generated objects
- * are assigned using global counter JsonPathExecContext.lastGeneratedObjectId.
- */
-static JsonPathExecResult
-executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonbValue *jb, JsonValueList *found)
+#define JsonItemInitNumericDatum(item, val) JsonItemInitNumeric(item, DatumGetNumeric(val))
+
+static void
+JsonItemInitString(JsonItem *item, char *str, int len)
{
- JsonPathExecResult res = jperNotFound;
- JsonPathItem next;
- JsonbContainer *jbc;
- JsonbValue key;
- JsonbValue val;
- JsonbValue idval;
- JsonbValue keystr;
- JsonbValue valstr;
- JsonbValue idstr;
- JsonbIterator *it;
- JsonbIteratorToken tok;
- int64 id;
- bool hasNext;
+ item->val.type = jbvString;
+ JsonItemString(item).val = str;
+ JsonItemString(item).len = len;
+}
- if (JsonbType(jb) != jbvObject || jb->type != jbvBinary)
- RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_JSON_OBJECT_NOT_FOUND),
- errmsg("jsonpath item method .%s() can only be applied to an object",
- jspOperationName(jsp->type)))));
+static void
+JsonItemInitDatetime(JsonItem *item, Datum val, Oid typid, int32 typmod, int tz)
+{
+ item->val.type = jsiDatetime;
+ JsonItemDatetime(item).value = val;
+ JsonItemDatetime(item).typid = typid;
+ JsonItemDatetime(item).typmod = typmod;
+ JsonItemDatetime(item).tz = tz;
+}
- jbc = jb->val.binary.data;
+static void
+JsonItemInitDouble(JsonItem *item, double val)
+{
+ item->val.type = jsiDouble;
+ JsonItemDouble(item) = val;
+}
- if (!JsonContainerSize(jbc))
- return jperNotFound; /* no key-value pairs */
+/********************Interface to pgsql's executor***************************/
- hasNext = jspGetNext(jsp, &next);
+bool
+JsonPathExists(Datum jb, JsonPath *jp, List *vars, bool isJsonb,
+ bool *error)
+{
+ Jsonx *js = DatumGetJsonxP(jb, isJsonb);
+ JsonPathExecResult res = executeJsonPath(jp, vars, EvalJsonPathVar,
+ js, isJsonb, !error,
+ NULL, NULL, NULL);
- keystr.type = jbvString;
- keystr.val.string.val = "key";
- keystr.val.string.len = 3;
+ Assert(error || !jperIsError(res));
- valstr.type = jbvString;
- valstr.val.string.val = "value";
- valstr.val.string.len = 5;
+ if (error && jperIsError(res))
+ *error = true;
- idstr.type = jbvString;
- idstr.val.string.val = "id";
- idstr.val.string.len = 2;
+ return res == jperOk;
+}
- /* construct object id from its base object and offset inside that */
- id = jb->type != jbvBinary ? 0 :
- (int64) ((char *) jbc - (char *) cxt->baseObject.jbc);
- id += (int64) cxt->baseObject.id * INT64CONST(10000000000);
+Datum
+JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty,
+ bool *error, List *vars, bool isJsonb)
+{
+ Jsonx *js = DatumGetJsonxP(jb, isJsonb);
+ JsonItem *first;
+ bool wrap;
+ JsonValueList found = {0};
+ JsonPathExecResult res PG_USED_FOR_ASSERTS_ONLY;
+ int count;
- idval.type = jbvNumeric;
- idval.val.numeric = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
- Int64GetDatum(id)));
+ res = executeJsonPath(jp, vars, EvalJsonPathVar, js, isJsonb, !error,
+ &found, NULL, NULL);
- it = JsonbIteratorInit(jbc);
+ Assert(error || !jperIsError(res));
- while ((tok = JsonbIteratorNext(&it, &key, true)) != WJB_DONE)
+ if (error && jperIsError(res))
{
- JsonBaseObjectInfo baseObject;
- JsonbValue obj;
- JsonbParseState *ps;
- JsonbValue *keyval;
- Jsonb *jsonb;
+ *error = true;
+ *empty = false;
+ return (Datum) 0;
+ }
- if (tok != WJB_KEY)
- continue;
+ 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 ||
+ JsonItemIsScalar(first) ||
+ (JsonItemIsBinary(first) &&
+ JsonContainerIsScalar(JsonItemBinary(first).data));
+ else
+ {
+ elog(ERROR, "unrecognized json wrapper %d", wrapper);
+ wrap = false;
+ }
- res = jperOk;
+ if (wrap)
+ {
+ JsonbValue *arr = wrapItemsInArray(&found, isJsonb);
- if (!hasNext && !found)
- break;
+ return JsonbValueToJsonxDatum(arr, isJsonb);
+ }
- tok = JsonbIteratorNext(&it, &val, true);
- Assert(tok == WJB_VALUE);
+ if (count > 1)
+ {
+ if (error)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
- ps = NULL;
- pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL);
+ ereport(ERROR,
+ (errcode(ERRCODE_MORE_THAN_ONE_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")));
+ }
- pushJsonbValue(&ps, WJB_KEY, &keystr);
- pushJsonbValue(&ps, WJB_VALUE, &key);
+ if (first)
+ return JsonItemToJsonxDatum(first, isJsonb);
- pushJsonbValue(&ps, WJB_KEY, &valstr);
- pushJsonbValue(&ps, WJB_VALUE, &val);
+ *empty = true;
+ return PointerGetDatum(NULL);
+}
- pushJsonbValue(&ps, WJB_KEY, &idstr);
- pushJsonbValue(&ps, WJB_VALUE, &idval);
+JsonItem *
+JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars,
+ bool isJsonb)
+{
+ Jsonx *js = DatumGetJsonxP(jb, isJsonb);
+ JsonItem *res;
+ JsonValueList found = { 0 };
+ JsonPathExecResult jper;
+ int count;
- keyval = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
+ jper = executeJsonPath(jp, vars, EvalJsonPathVar, js, isJsonb, !error,
+ &found, NULL, NULL);
- jsonb = JsonbValueToJsonb(keyval);
+ Assert(error || !jperIsError(jper));
- JsonbInitBinary(&obj, jsonb);
+ if (error && jperIsError(jper))
+ {
+ *error = true;
+ *empty = false;
+ return NULL;
+ }
- baseObject = setBaseObject(cxt, &obj, cxt->lastGeneratedObjectId++);
+ count = JsonValueListLength(&found);
- res = executeNextItem(cxt, jsp, &next, &obj, found, true);
+ *empty = !count;
- cxt->baseObject = baseObject;
+ if (*empty)
+ return NULL;
- if (jperIsError(res))
- return res;
+ if (count > 1)
+ {
+ if (error)
+ {
+ *error = true;
+ return NULL;
+ }
- if (res == jperOk && !found)
- break;
+ ereport(ERROR,
+ (errcode(ERRCODE_MORE_THAN_ONE_JSON_ITEM),
+ errmsg("JSON path expression in JSON_VALUE should return "
+ "singleton scalar item")));
}
- return res;
-}
-
-/*
- * Convert boolean execution status 'res' to a boolean JSON item and execute
- * next jsonpath.
- */
-static JsonPathExecResult
-appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonValueList *found, JsonPathBool res)
-{
- JsonPathItem next;
- JsonbValue jbv;
-
- if (!jspGetNext(jsp, &next) && !found)
- return jperOk; /* found singleton boolean value */
+ res = JsonValueListHead(&found);
- if (res == jpbUnknown)
+ if (JsonItemIsBinary(res) &&
+ JsonContainerIsScalar(JsonItemBinary(res).data))
{
- jbv.type = jbvNull;
+ if (isJsonb)
+ JsonbExtractScalar(JsonItemBinary(res).data, JsonItemJbv(res));
+ else
+ JsonExtractScalar((JsonContainer *) JsonItemBinary(res).data, JsonItemJbv(res));
}
- else
+
+ if (!JsonItemIsScalar(res))
{
- jbv.type = jbvBool;
- jbv.val.boolean = res == jpbTrue;
+ if (error)
+ {
+ *error = true;
+ return NULL;
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_JSON_SCALAR_REQUIRED),
+ errmsg("JSON path expression in JSON_VALUE should return "
+ "singleton scalar item")));
}
- return executeNextItem(cxt, jsp, &next, &jbv, found, true);
+ if (JsonItemIsNull(res))
+ return NULL;
+
+ return res;
}
-/*
- * Convert jsonpath's scalar or variable node to actual jsonb value.
- *
- * If node is a variable then its id returned, otherwise 0 returned.
- */
-static void
-getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
- JsonbValue *value)
+void
+JsonItemFromDatum(Datum val, Oid typid, int32 typmod, JsonItem *res,
+ bool isJsonb)
{
- switch (item->type)
+ switch (typid)
{
- case jpiNull:
- value->type = jbvNull;
+ case BOOLOID:
+ JsonItemInitBool(res, DatumGetBool(val));
break;
- case jpiBool:
- value->type = jbvBool;
- value->val.boolean = jspGetBool(item);
+ case NUMERICOID:
+ JsonItemInitNumericDatum(res, val);
break;
- case jpiNumeric:
- value->type = jbvNumeric;
- value->val.numeric = jspGetNumeric(item);
+ case INT2OID:
+ JsonItemInitNumericDatum(res, DirectFunctionCall1(int2_numeric, val));
break;
- case jpiString:
- value->type = jbvString;
- value->val.string.val = jspGetString(item,
- &value->val.string.len);
+ case INT4OID:
+ JsonItemInitNumericDatum(res, DirectFunctionCall1(int4_numeric, val));
break;
- case jpiVariable:
- getJsonPathVariable(cxt, item, cxt->vars, value);
- return;
- default:
- elog(ERROR, "unexpected jsonpath item type");
- }
-}
+ case INT8OID:
+ JsonItemInitNumericDatum(res, DirectFunctionCall1(int8_numeric, val));
+ break;
+ case FLOAT4OID:
+ JsonItemInitDouble(res, DatumGetFloat4(val));
+ break;
+ case FLOAT8OID:
+ JsonItemInitDouble(res, DatumGetFloat8(val));
+ break;
+ case TEXTOID:
+ case VARCHAROID:
+ JsonItemInitString(res, VARDATA_ANY(val), VARSIZE_ANY_EXHDR(val));
+ break;
+ case DATEOID:
+ case TIMEOID:
+ case TIMETZOID:
+ case TIMESTAMPOID:
+ case TIMESTAMPTZOID:
+ JsonItemInitDatetime(res, val, typid, typmod, 0);
+ break;
+ case JSONBOID:
+ {
+ JsonbValue *jbv = JsonItemJbv(res);
+ Jsonb *jb = DatumGetJsonbP(val);
-/*
- * Get the value of variable passed to jsonpath executor
- */
-static void
-getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable,
- Jsonb *vars, JsonbValue *value)
-{
- char *varName;
- int varNameLength;
- JsonbValue tmp;
- JsonbValue *v;
+ if (JsonContainerIsScalar(&jb->root))
+ {
+ bool res PG_USED_FOR_ASSERTS_ONLY;
- if (!vars)
- {
- value->type = jbvNull;
- return;
- }
+ res = JsonbExtractScalar(&jb->root, jbv);
+ Assert(res);
+ }
+ else if (isJsonb)
+ {
+ JsonbInitBinary(jbv, jb);
+ }
+ else
+ {
+ StringInfoData buf;
+ text *txt;
+ Json *js;
- Assert(variable->type == jpiVariable);
- varName = jspGetString(variable, &varNameLength);
- tmp.type = jbvString;
- tmp.val.string.val = varName;
- tmp.val.string.len = varNameLength;
+ initStringInfo(&buf);
+ JsonbToCString(&buf, &jb->root, VARSIZE(jb));
+ txt = cstring_to_text_with_len(buf.data, buf.len);
+ pfree(buf.data);
- v = findJsonbValueFromContainer(&vars->root, JB_FOBJECT, &tmp);
+ js = JsonCreate(txt);
- if (v)
- {
- *value = *v;
- pfree(v);
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("could not find jsonpath variable \"%s\"",
- pnstrdup(varName, varNameLength))));
- }
+ JsonInitBinary(jbv, js);
+ }
+ break;
+ }
+ case JSONOID:
+ {
+ JsonbValue *jbv = JsonItemJbv(res);
+ Json *js = DatumGetJsonP(val);
+
+ if (JsonContainerIsScalar(&js->root))
+ {
+ bool res PG_USED_FOR_ASSERTS_ONLY;
- JsonbInitBinary(&tmp, vars);
- setBaseObject(cxt, &tmp, 1);
+ res = JsonExtractScalar(&js->root, jbv);
+ Assert(res);
+ }
+ else if (isJsonb)
+ {
+ text *txt = DatumGetTextP(val);
+ char *str = text_to_cstring(txt);
+ Jsonb *jb =
+ DatumGetJsonbP(DirectFunctionCall1(jsonb_in, CStringGetDatum(str)));
+
+ pfree(str);
+
+ JsonbInitBinary(jbv, jb);
+ }
+ else
+ {
+ JsonInitBinary(jbv, js);
+ }
+ break;
+ }
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("only bool, numeric and text types could be "
+ "casted to supported jsonpath types.")));
+ }
}
-/**************** Support functions for JsonPath execution *****************/
+/************************ JSON_TABLE functions ***************************/
/*
- * Returns the size of an array item, or -1 if item is not an array.
+ * Returns private data from executor state. Ensure validity by check with
+ * MAGIC number.
*/
-static int
-JsonbArraySize(JsonbValue *jb)
+static inline JsonTableContext *
+GetJsonTableContext(TableFuncScanState *state, const char *fname)
{
- Assert(jb->type != jbvArray);
-
- if (jb->type == jbvBinary)
- {
- JsonbContainer *jbc = jb->val.binary.data;
+ JsonTableContext *result;
- if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc))
- return JsonContainerSize(jbc);
- }
+ if (!IsA(state, TableFuncScanState))
+ elog(ERROR, "%s called with invalid TableFuncScanState", fname);
+ result = (JsonTableContext *) state->opaque;
+ if (result->magic != JSON_TABLE_CONTEXT_MAGIC)
+ elog(ERROR, "%s called with invalid TableFuncScanState", fname);
- return -1;
+ return result;
}
-/* Comparison predicate callback. */
-static JsonPathBool
-executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
+/* Recursively initialize JSON_TABLE scan state */
+static void
+JsonTableInitScanState(JsonTableContext *cxt, JsonTableScanState *scan,
+ JsonTableParentNode *node, JsonTableScanState *parent,
+ List *args, MemoryContext mcxt)
{
- return compareItems(cmp->type, lv, rv);
+ int i;
+
+ scan->parent = parent;
+ scan->outerJoin = node->outerJoin;
+ scan->errorOnError = node->errorOnError;
+ scan->path = DatumGetJsonPathP(node->path->constvalue);
+ scan->args = args;
+ scan->mcxt = AllocSetContextCreate(mcxt, "JsonTableContext",
+ ALLOCSET_DEFAULT_SIZES);
+ scan->nested = node->child ?
+ JsonTableInitPlanState(cxt, node->child, scan) : NULL;
+ scan->current = PointerGetDatum(NULL);
+ scan->currentIsNull = true;
+
+ for (i = node->colMin; i <= node->colMax; i++)
+ cxt->colexprs[i].scan = scan;
}
-/*
- * Compare two SQL/JSON items using comparison operation 'op'.
- */
-static JsonPathBool
-compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+/* Recursively initialize JSON_TABLE scan state */
+static JsonTableJoinState *
+JsonTableInitPlanState(JsonTableContext *cxt, Node *plan,
+ JsonTableScanState *parent)
{
- int cmp;
- bool res;
+ JsonTableJoinState *state = palloc0(sizeof(*state));
- if (jb1->type != jb2->type)
+ if (IsA(plan, JsonTableSiblingNode))
{
- if (jb1->type == jbvNull || jb2->type == jbvNull)
+ JsonTableSiblingNode *join = castNode(JsonTableSiblingNode, plan);
- /*
- * Equality and order comparison of nulls to non-nulls returns
- * always false, but inequality comparison returns true.
- */
- return op == jpiNotEqual ? jpbTrue : jpbFalse;
+ state->is_join = true;
+ state->u.join.cross = join->cross;
+ state->u.join.left = JsonTableInitPlanState(cxt, join->larg, parent);
+ state->u.join.right = JsonTableInitPlanState(cxt, join->rarg, parent);
+ }
+ else
+ {
+ JsonTableParentNode *node = castNode(JsonTableParentNode, plan);
- /* Non-null items of different types are not comparable. */
- return jpbUnknown;
+ state->is_join = false;
+
+ JsonTableInitScanState(cxt, &state->u.scan, node, parent,
+ parent->args, parent->mcxt);
}
- switch (jb1->type)
+ return state;
+}
+
+/*
+ * JsonxTableInitOpaque
+ * Fill in TableFuncScanState->opaque for JsonTable processor
+ */
+static void
+JsonxTableInitOpaque(TableFuncScanState *state, int natts, bool isJsonb)
+{
+ JsonTableContext *cxt;
+ PlanState *ps = &state->ss.ps;
+ TableFuncScan *tfs = castNode(TableFuncScan, ps->plan);
+ TableFunc *tf = tfs->tablefunc;
+ JsonExpr *ci = castNode(JsonExpr, tf->docexpr);
+ JsonTableParentNode *root = castNode(JsonTableParentNode, tf->plan);
+ List *args = NIL;
+ ListCell *lc;
+ int i;
+
+ cxt = palloc0(sizeof(JsonTableContext));
+ cxt->magic = JSON_TABLE_CONTEXT_MAGIC;
+ cxt->isJsonb = isJsonb;
+
+ if (list_length(ci->passing.values) > 0)
{
- case jbvNull:
- cmp = 0;
- break;
- case jbvBool:
- cmp = jb1->val.boolean == jb2->val.boolean ? 0 :
- jb1->val.boolean ? 1 : -1;
- break;
- case jbvNumeric:
- cmp = compareNumeric(jb1->val.numeric, jb2->val.numeric);
- break;
- case jbvString:
- if (op == jpiEqual)
- return jb1->val.string.len != jb2->val.string.len ||
- memcmp(jb1->val.string.val,
- jb2->val.string.val,
- jb1->val.string.len) ? jpbFalse : jpbTrue;
+ ListCell *exprlc;
+ ListCell *namelc;
- cmp = varstr_cmp(jb1->val.string.val, jb1->val.string.len,
- jb2->val.string.val, jb2->val.string.len,
- DEFAULT_COLLATION_OID);
- break;
+ forboth(exprlc, ci->passing.values,
+ namelc, ci->passing.names)
+ {
+ Expr *expr = (Expr *) lfirst(exprlc);
+ Value *name = (Value *) lfirst(namelc);
+ JsonPathVariableEvalContext *var = palloc(sizeof(*var));
+
+ var->name = pstrdup(name->val.str);
+ var->typid = exprType((Node *) expr);
+ var->typmod = exprTypmod((Node *) expr);
+ var->estate = ExecInitExpr(expr, ps);
+ var->econtext = ps->ps_ExprContext;
+ var->mcxt = CurrentMemoryContext;
+ var->evaluated = false;
+ var->value = (Datum) 0;
+ var->isnull = true;
+
+ args = lappend(args, var);
+ }
+ }
- case jbvBinary:
- case jbvArray:
- case jbvObject:
- return jpbUnknown; /* non-scalars are not comparable */
+ cxt->colexprs = palloc(sizeof(*cxt->colexprs) *
+ list_length(tf->colvalexprs));
- default:
- elog(ERROR, "invalid jsonb value type %d", jb1->type);
- }
+ JsonTableInitScanState(cxt, &cxt->root, root, NULL, args,
+ CurrentMemoryContext);
- switch (op)
+ i = 0;
+
+ foreach(lc, tf->colvalexprs)
{
- case jpiEqual:
- res = (cmp == 0);
- break;
- case jpiNotEqual:
- res = (cmp != 0);
- break;
- case jpiLess:
- res = (cmp < 0);
- break;
- case jpiGreater:
- res = (cmp > 0);
- break;
- case jpiLessOrEqual:
- res = (cmp <= 0);
- break;
- case jpiGreaterOrEqual:
- res = (cmp >= 0);
- break;
- default:
- elog(ERROR, "unrecognized jsonpath operation: %d", op);
- return jpbUnknown;
+ Expr *expr = lfirst(lc);
+
+ cxt->colexprs[i].expr =
+ ExecInitExprWithCaseValue(expr, ps,
+ &cxt->colexprs[i].scan->current,
+ &cxt->colexprs[i].scan->currentIsNull);
+
+ i++;
}
- return res ? jpbTrue : jpbFalse;
+ state->opaque = cxt;
}
-/* Compare two numerics */
-static int
-compareNumeric(Numeric a, Numeric b)
+static void
+JsonbTableInitOpaque(TableFuncScanState *state, int natts)
{
- return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
- NumericGetDatum(a),
- NumericGetDatum(b)));
+ JsonxTableInitOpaque(state, natts, true);
}
-static JsonbValue *
-copyJsonbValue(JsonbValue *src)
+static void
+JsonTableInitOpaque(TableFuncScanState *state, int natts)
{
- JsonbValue *dst = palloc(sizeof(*dst));
-
- *dst = *src;
+ JsonxTableInitOpaque(state, natts, false);
+}
- return dst;
+/* Reset scan iterator to the beginning of the item list */
+static void
+JsonTableRescan(JsonTableScanState *scan)
+{
+ JsonValueListInitIterator(&scan->found, &scan->iter);
+ scan->current = PointerGetDatum(NULL);
+ scan->currentIsNull = true;
+ scan->advanceNested = false;
+ scan->ordinal = 0;
}
-/*
- * Execute array subscript expression and convert resulting numeric item to
- * the integer type with truncation.
- */
-static JsonPathExecResult
-getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
- int32 *index)
+/* Reset context item of a scan, execute JSON path and reset a scan */
+static void
+JsonTableResetContextItem(JsonTableScanState *scan, Datum item, bool isJsonb)
{
- JsonbValue *jbv;
- JsonValueList found = {0};
- JsonPathExecResult res = executeItem(cxt, jsp, jb, &found);
- Datum numeric_index;
- bool have_error = false;
+ MemoryContext oldcxt;
+ JsonPathExecResult res;
+ Jsonx *js = DatumGetJsonxP(item, isJsonb);
- if (jperIsError(res))
- return res;
+ JsonValueListClear(&scan->found);
- if (JsonValueListLength(&found) != 1 ||
- !(jbv = getScalar(JsonValueListHead(&found), jbvNumeric)))
- RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
- errmsg("jsonpath array subscript is not a single numeric value"))));
+ MemoryContextResetOnly(scan->mcxt);
- numeric_index = DirectFunctionCall2(numeric_trunc,
- NumericGetDatum(jbv->val.numeric),
- Int32GetDatum(0));
+ oldcxt = MemoryContextSwitchTo(scan->mcxt);
- *index = numeric_int4_opt_error(DatumGetNumeric(numeric_index),
- &have_error);
+ res = executeJsonPath(scan->path, scan->args, EvalJsonPathVar, js, isJsonb,
+ scan->errorOnError, &scan->found,
+ NULL /* FIXME */, NULL);
- if (have_error)
- RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
- errmsg("jsonpath array subscript is out of integer range"))));
+ MemoryContextSwitchTo(oldcxt);
- return jperOk;
+ if (jperIsError(res))
+ {
+ Assert(!scan->errorOnError);
+ JsonValueListClear(&scan->found); /* EMPTY ON ERROR case */
+ }
+
+ JsonTableRescan(scan);
}
-/* Save base object and its id needed for the execution of .keyvalue(). */
-static JsonBaseObjectInfo
-setBaseObject(JsonPathExecContext *cxt, JsonbValue *jbv, int32 id)
+/*
+ * JsonTableSetDocument
+ * Install the input document
+ */
+static void
+JsonTableSetDocument(TableFuncScanState *state, Datum value)
{
- JsonBaseObjectInfo baseObject = cxt->baseObject;
+ JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableSetDocument");
- cxt->baseObject.jbc = jbv->type != jbvBinary ? NULL :
- (JsonbContainer *) jbv->val.binary.data;
- cxt->baseObject.id = id;
-
- return baseObject;
+ JsonTableResetContextItem(&cxt->root, value, cxt->isJsonb);
}
+/* Recursively reset scan and its child nodes */
static void
-JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv)
+JsonTableRescanRecursive(JsonTableJoinState *state)
{
- if (jvl->singleton)
+ if (state->is_join)
{
- jvl->list = list_make2(jvl->singleton, jbv);
- jvl->singleton = NULL;
+ JsonTableRescanRecursive(state->u.join.left);
+ JsonTableRescanRecursive(state->u.join.right);
+ state->u.join.advanceRight = false;
}
- else if (!jvl->list)
- jvl->singleton = jbv;
else
- jvl->list = lappend(jvl->list, jbv);
-}
-
-static int
-JsonValueListLength(const JsonValueList *jvl)
-{
- return jvl->singleton ? 1 : list_length(jvl->list);
+ {
+ JsonTableRescan(&state->u.scan);
+ if (state->u.scan.nested)
+ JsonTableRescanRecursive(state->u.scan.nested);
+ }
}
+/*
+ * Fetch next row from a cross/union joined scan.
+ *
+ * Returned false at the end of a scan, true otherwise.
+ */
static bool
-JsonValueListIsEmpty(JsonValueList *jvl)
+JsonTableNextJoinRow(JsonTableJoinState *state, bool isJsonb)
{
- return !jvl->singleton && list_length(jvl->list) <= 0;
-}
+ if (!state->is_join)
+ return JsonTableNextRow(&state->u.scan, isJsonb);
-static JsonbValue *
-JsonValueListHead(JsonValueList *jvl)
-{
- return jvl->singleton ? jvl->singleton : linitial(jvl->list);
-}
+ if (state->u.join.advanceRight)
+ {
+ /* fetch next inner row */
+ if (JsonTableNextJoinRow(state->u.join.right, isJsonb))
+ return true;
-static List *
-JsonValueListGetList(JsonValueList *jvl)
-{
- if (jvl->singleton)
- return list_make1(jvl->singleton);
+ /* inner rows are exhausted */
+ if (state->u.join.cross)
+ state->u.join.advanceRight = false; /* next outer row */
+ else
+ return false; /* end of scan */
+ }
+
+ while (!state->u.join.advanceRight)
+ {
+ /* fetch next outer row */
+ bool left = JsonTableNextJoinRow(state->u.join.left, isJsonb);
+
+ if (state->u.join.cross)
+ {
+ if (!left)
+ return false; /* end of scan */
+
+ JsonTableRescanRecursive(state->u.join.right);
+
+ if (!JsonTableNextJoinRow(state->u.join.right, isJsonb))
+ continue; /* next outer row */
+
+ state->u.join.advanceRight = true; /* next inner row */
+ }
+ else if (!left)
+ {
+ if (!JsonTableNextJoinRow(state->u.join.right, isJsonb))
+ return false; /* end of scan */
+
+ state->u.join.advanceRight = true; /* next inner row */
+ }
- return jvl->list;
+ break;
+ }
+
+ return true;
}
+/* Recursively set 'reset' flag of scan and its child nodes */
static void
-JsonValueListInitIterator(const JsonValueList *jvl, JsonValueListIterator *it)
+JsonTableJoinReset(JsonTableJoinState *state)
{
- if (jvl->singleton)
- {
- it->value = jvl->singleton;
- it->next = NULL;
- }
- else if (list_head(jvl->list) != NULL)
+ if (state->is_join)
{
- it->value = (JsonbValue *) linitial(jvl->list);
- it->next = lnext(list_head(jvl->list));
+ JsonTableJoinReset(state->u.join.left);
+ JsonTableJoinReset(state->u.join.right);
+ state->u.join.advanceRight = false;
}
else
{
- it->value = NULL;
- it->next = NULL;
+ state->u.scan.reset = true;
+ state->u.scan.advanceNested = false;
+
+ if (state->u.scan.nested)
+ JsonTableJoinReset(state->u.scan.nested);
}
}
/*
- * Get the next item from the sequence advancing iterator.
+ * Fetch next row from a simple scan with outer/inner joined nested subscans.
+ *
+ * Returned false at the end of a scan, true otherwise.
*/
-static JsonbValue *
-JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it)
+static bool
+JsonTableNextRow(JsonTableScanState *scan, bool isJsonb)
{
- JsonbValue *result = it->value;
+ /* reset context item if requested */
+ if (scan->reset)
+ {
+ Assert(!scan->parent->currentIsNull);
+ JsonTableResetContextItem(scan, scan->parent->current, isJsonb);
+ scan->reset = false;
+ }
- if (it->next)
+ if (scan->advanceNested)
{
- it->value = lfirst(it->next);
- it->next = lnext(it->next);
+ /* fetch next nested row */
+ scan->advanceNested = JsonTableNextJoinRow(scan->nested, isJsonb);
+
+ if (scan->advanceNested)
+ return true;
}
- else
+
+ for (;;)
{
- it->value = NULL;
+ /* fetch next row */
+ JsonItem *jbv = JsonValueListNext(&scan->found, &scan->iter);
+ MemoryContext oldcxt;
+
+ if (!jbv)
+ {
+ scan->current = PointerGetDatum(NULL);
+ scan->currentIsNull = true;
+ return false; /* end of scan */
+ }
+
+ /* set current row item */
+ oldcxt = MemoryContextSwitchTo(scan->mcxt);
+ scan->current = JsonItemToJsonxDatum(jbv, isJsonb);
+ scan->currentIsNull = false;
+ MemoryContextSwitchTo(oldcxt);
+
+ scan->ordinal++;
+
+ if (!scan->nested)
+ break;
+
+ JsonTableJoinReset(scan->nested);
+
+ scan->advanceNested = JsonTableNextJoinRow(scan->nested, isJsonb);
+
+ if (scan->advanceNested || scan->outerJoin)
+ break;
+
+ /* state->ordinal--; */ /* skip current outer row, reset counter */
}
- return result;
+ return true;
}
/*
- * Initialize a binary JsonbValue with the given jsonb container.
+ * JsonTableFetchRow
+ * Prepare the next "current" tuple for upcoming GetValue calls.
+ * Returns FALSE if the row-filter expression returned no more rows.
*/
-static JsonbValue *
-JsonbInitBinary(JsonbValue *jbv, Jsonb *jb)
+static bool
+JsonTableFetchRow(TableFuncScanState *state)
{
- jbv->type = jbvBinary;
- jbv->val.binary.data = &jb->root;
- jbv->val.binary.len = VARSIZE_ANY_EXHDR(jb);
+ JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableFetchRow");
- return jbv;
+ if (cxt->empty)
+ return false;
+
+ return JsonTableNextRow(&cxt->root, cxt->isJsonb);
}
/*
- * Returns jbv* type of of JsonbValue. Note, it never returns jbvBinary as is.
+ * JsonTableGetValue
+ * Return the value for column number 'colnum' for the current row.
+ *
+ * This leaks memory, so be sure to reset often the context in which it's
+ * called.
*/
-static int
-JsonbType(JsonbValue *jb)
+static Datum
+JsonTableGetValue(TableFuncScanState *state, int colnum,
+ Oid typid, int32 typmod, bool *isnull)
{
- int type = jb->type;
+ JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableGetValue");
+ ExprContext *econtext = state->ss.ps.ps_ExprContext;
+ ExprState *estate = cxt->colexprs[colnum].expr;
+ JsonTableScanState *scan = cxt->colexprs[colnum].scan;
+ Datum result;
- if (jb->type == jbvBinary)
+ if (scan->currentIsNull) /* NULL from outer/union join */
{
- JsonbContainer *jbc = (void *) jb->val.binary.data;
-
- /* Scalars should be always extracted during jsonpath execution. */
- Assert(!JsonContainerIsScalar(jbc));
-
- if (JsonContainerIsObject(jbc))
- type = jbvObject;
- else if (JsonContainerIsArray(jbc))
- type = jbvArray;
- else
- elog(ERROR, "invalid jsonb container type: 0x%08x", jbc->header);
+ result = (Datum) 0;
+ *isnull = true;
+ }
+ else if (estate) /* regular column */
+ {
+ result = ExecEvalExpr(estate, econtext, isnull);
+ }
+ else
+ {
+ result = Int32GetDatum(scan->ordinal); /* ordinality column */
+ *isnull = false;
}
- return type;
+ return result;
}
-/* Get scalar of given type or NULL on type mismatch */
-static JsonbValue *
-getScalar(JsonbValue *scalar, enum jbvType type)
+/*
+ * JsonTableDestroyOpaque
+ */
+static void
+JsonTableDestroyOpaque(TableFuncScanState *state)
{
- /* Scalars should be always extracted during jsonpath execution. */
- Assert(scalar->type != jbvBinary ||
- !JsonContainerIsScalar(scalar->val.binary.data));
+ JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableDestroyOpaque");
- return scalar->type == type ? scalar : NULL;
+ /* not valid anymore */
+ cxt->magic = 0;
+
+ state->opaque = NULL;
}
-/* Construct a JSON array from the item list */
-static JsonbValue *
-wrapItemsInArray(const JsonValueList *items)
+const TableFuncRoutine JsonbTableRoutine =
{
- JsonbParseState *ps = NULL;
- JsonValueListIterator it;
- JsonbValue *jbv;
-
- pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL);
-
- JsonValueListInitIterator(items, &it);
- while ((jbv = JsonValueListNext(items, &it)))
- pushJsonbValue(&ps, WJB_ELEM, jbv);
-
- return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
-}
+ JsonbTableInitOpaque,
+ JsonTableSetDocument,
+ NULL,
+ NULL,
+ NULL,
+ JsonTableFetchRow,
+ JsonTableGetValue,
+ JsonTableDestroyOpaque
+};
+
+const TableFuncRoutine JsonTableRoutine =
+{
+ JsonTableInitOpaque,
+ JsonTableSetDocument,
+ NULL,
+ NULL,
+ NULL,
+ JsonTableFetchRow,
+ JsonTableGetValue,
+ JsonTableDestroyOpaque
+};
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 22c2089f78..add754e6eb 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -43,6 +43,7 @@ static JsonPathParseItem *makeItemType(JsonPathItemType type);
static JsonPathParseItem *makeItemString(JsonPathString *s);
static JsonPathParseItem *makeItemVariable(JsonPathString *s);
static JsonPathParseItem *makeItemKey(JsonPathString *s);
+static JsonPathParseItem *makeItemArgument(JsonPathString *s);
static JsonPathParseItem *makeItemNumeric(JsonPathString *s);
static JsonPathParseItem *makeItemBool(bool val);
static JsonPathParseItem *makeItemBinary(JsonPathItemType type,
@@ -50,12 +51,20 @@ static JsonPathParseItem *makeItemBinary(JsonPathItemType type,
JsonPathParseItem *ra);
static JsonPathParseItem *makeItemUnary(JsonPathItemType type,
JsonPathParseItem *a);
+static JsonPathParseItem *makeItemFunc(JsonPathString *name, List *args,
+ bool method);
static JsonPathParseItem *makeItemList(List *list);
static JsonPathParseItem *makeIndexArray(List *list);
static JsonPathParseItem *makeAny(int first, int last);
static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
JsonPathString *pattern,
JsonPathString *flags);
+static JsonPathParseItem *makeItemSequence(List *elems);
+static JsonPathParseItem *makeItemObject(List *fields);
+static JsonPathParseItem *makeItemCurrentN(int level);
+static JsonPathParseItem *makeItemLambda(List *params, JsonPathParseItem *expr);
+static JsonPathParseItem *setItemOutPathMode(JsonPathParseItem *jpi);
+static List *setItemsOutPathMode(List *items);
/*
* Bison doesn't allocate anything that needs to live across parser calls,
@@ -90,18 +99,22 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
%token TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
%token IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
-%token OR_P AND_P NOT_P
+%token OR_P AND_P NOT_P ARROW_P
%token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
%token ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
%token ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
+%token DATETIME_P CURRENT_P
%type result
%type scalar_value path_primary expr array_accessor
any_path accessor_op key predicate delimited_predicate
index_elem starts_with_initial expr_or_predicate
+ datetime_template opt_datetime_template expr_seq expr_or_seq
+ object_field lambda_expr lambda_or_expr
-%type accessor_expr
+%type accessor_expr accessor_ops expr_list object_field_list
+ lambda_or_expr_list
%type index_list
@@ -109,7 +122,7 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
%type mode
-%type key_name
+%type key_name method_name builtin_method_name function_name
%type any_level
@@ -125,7 +138,7 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
%%
result:
- mode expr_or_predicate {
+ mode expr_or_seq {
*result = palloc(sizeof(JsonPathParseResult));
(*result)->expr = $2;
(*result)->lax = $1;
@@ -138,6 +151,39 @@ expr_or_predicate:
| predicate { $$ = $1; }
;
+expr_or_seq:
+ expr_or_predicate { $$ = $1; }
+ | expr_seq { $$ = $1; }
+ ;
+
+expr_seq:
+ expr_list { $$ = makeItemSequence($1); }
+ ;
+
+expr_list:
+ expr_or_predicate ',' expr_or_predicate { $$ = list_make2($1, $3); }
+ | expr_list ',' expr_or_predicate { $$ = lappend($1, $3); }
+ ;
+
+
+lambda_expr:
+ IDENT_P ARROW_P expr { $$ = makeItemLambda(list_make1(
+ makeItemArgument(&$1)), $3); }
+ | '(' expr ')' ARROW_P expr { $$ = makeItemLambda(list_make1($2), $5); }
+ | '(' ')' ARROW_P expr { $$ = makeItemLambda(NIL, $4); }
+ | '(' expr_seq ')' ARROW_P expr { $$ = makeItemLambda($2->value.sequence.elems, $5); }
+ ;
+
+lambda_or_expr:
+ expr
+ | lambda_expr
+ ;
+
+lambda_or_expr_list:
+ /* EMPYT*/ { $$ = NIL; }
+ | lambda_or_expr { $$ = list_make1($1); }
+ | lambda_or_expr_list ',' lambda_or_expr { $$ = lappend($1, $3); }
+ ;
mode:
STRICT_P { $$ = false; }
| LAX_P { $$ = true; }
@@ -152,6 +198,7 @@ scalar_value:
| NUMERIC_P { $$ = makeItemNumeric(&$1); }
| INT_P { $$ = makeItemNumeric(&$1); }
| VARIABLE_P { $$ = makeItemVariable(&$1); }
+ | IDENT_P { $$ = makeItemArgument(&$1); }
;
comp_op:
@@ -192,7 +239,25 @@ path_primary:
scalar_value { $$ = $1; }
| '$' { $$ = makeItemType(jpiRoot); }
| '@' { $$ = makeItemType(jpiCurrent); }
+ | CURRENT_P { $$ = makeItemCurrentN(pg_atoi(&$1.val[1], 4, 0)); }
| LAST_P { $$ = makeItemType(jpiLast); }
+ | '(' expr_seq ')' { $$ = $2; }
+ | '[' ']' { $$ = makeItemUnary(jpiArray, NULL); }
+ | '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); }
+ | '{' object_field_list '}' { $$ = makeItemObject($2); }
+ | function_name '(' lambda_or_expr_list ')'
+ { $$ = makeItemFunc(&$1, $3, false); }
+ ;
+
+object_field_list:
+ /* EMPTY */ { $$ = NIL; }
+ | object_field { $$ = list_make1($1); }
+ | object_field_list ',' object_field { $$ = lappend($1, $3); }
+ ;
+
+object_field:
+ key_name ':' expr_or_predicate
+ { $$ = makeItemBinary(jpiObjectField, makeItemString(&$1), $3); }
;
accessor_expr:
@@ -200,6 +265,27 @@ accessor_expr:
| '(' expr ')' accessor_op { $$ = list_make2($2, $4); }
| '(' predicate ')' accessor_op { $$ = list_make2($2, $4); }
| accessor_expr accessor_op { $$ = lappend($1, $2); }
+ | accessor_expr '.' '(' key ')'
+ { $$ = lappend($1, setItemOutPathMode($4)); }
+ | accessor_expr '.' '(' key accessor_ops ')'
+ { $$ = list_concat($1, setItemsOutPathMode(lcons($4, $5))); }
+ | accessor_expr '.' '(' '*' ')'
+ { $$ = lappend($1, setItemOutPathMode(makeItemType(jpiAnyKey))); }
+ | accessor_expr '.' '(' '*' accessor_ops ')'
+ { $$ = list_concat($1, setItemsOutPathMode(lcons(makeItemType(jpiAnyKey), $5))); }
+ | accessor_expr '.' '(' array_accessor ')'
+ { $$ = lappend($1, setItemOutPathMode($4)); }
+ | accessor_expr '.' '(' array_accessor accessor_ops ')'
+ { $$ = list_concat($1, setItemsOutPathMode(lcons($4, $5))); }
+ | accessor_expr '.' '(' any_path ')'
+ { $$ = lappend($1, setItemOutPathMode($4)); }
+ | accessor_expr '.' '(' any_path accessor_ops ')'
+ { $$ = list_concat($1, setItemsOutPathMode(lcons($4, $5))); }
+ ;
+
+accessor_ops:
+ accessor_op { $$ = list_make1($1); }
+ | accessor_ops accessor_op { $$ = lappend($1, $2); }
;
expr:
@@ -247,14 +333,46 @@ accessor_op:
| array_accessor { $$ = $1; }
| '.' any_path { $$ = $2; }
| '.' method '(' ')' { $$ = makeItemType($2); }
+ | '.' DATETIME_P '(' opt_datetime_template ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+ | '.' DATETIME_P '(' datetime_template ',' expr ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, $6); }
| '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
+ | '.' method_name '(' lambda_or_expr_list ')'
+ { $$ = makeItemFunc(&$2, $4, true); }
+ ;
+
+datetime_template:
+ STRING_P { $$ = makeItemString(&$1); }
+ ;
+
+opt_datetime_template:
+ datetime_template { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
;
key:
key_name { $$ = makeItemKey(&$1); }
;
-key_name:
+function_name:
+ IDENT_P
+ | STRING_P
+/* | TO_P */
+ | NULL_P
+ | TRUE_P
+ | FALSE_P
+ | IS_P
+ | UNKNOWN_P
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ | builtin_method_name
+ ;
+
+method_name:
IDENT_P
| STRING_P
| TO_P
@@ -266,18 +384,27 @@ key_name:
| EXISTS_P
| STRICT_P
| LAX_P
- | ABS_P
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+builtin_method_name:
+ ABS_P
| SIZE_P
| TYPE_P
| FLOOR_P
| DOUBLE_P
| CEILING_P
+ | DATETIME_P
| KEYVALUE_P
- | LAST_P
- | STARTS_P
- | WITH_P
- | LIKE_REGEX_P
- | FLAG_P
+ ;
+
+key_name:
+ method_name
+ | builtin_method_name
;
method:
@@ -304,11 +431,26 @@ makeItemType(JsonPathItemType type)
CHECK_FOR_INTERRUPTS();
v->type = type;
+ v->flags = 0;
v->next = NULL;
return v;
}
+static JsonPathParseItem *
+makeItemCurrentN(int level)
+{
+ JsonPathParseItem *v;
+
+ if (!level)
+ return makeItemType(jpiCurrent);
+
+ v = makeItemType(jpiCurrentN);
+ v->value.current.level = level;
+
+ return v;
+}
+
static JsonPathParseItem *
makeItemString(JsonPathString *s)
{
@@ -351,6 +493,17 @@ makeItemKey(JsonPathString *s)
return v;
}
+static JsonPathParseItem *
+makeItemArgument(JsonPathString *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemString(s);
+ v->type = jpiArgument;
+
+ return v;
+}
+
static JsonPathParseItem *
makeItemNumeric(JsonPathString *s)
{
@@ -411,6 +564,18 @@ makeItemUnary(JsonPathItemType type, JsonPathParseItem *a)
return v;
}
+static JsonPathParseItem *
+makeItemFunc(JsonPathString *name, List *args, bool method)
+{
+ JsonPathParseItem *v = makeItemType(method ? jpiMethod : jpiFunction);
+
+ v->value.func.name = name->val;
+ v->value.func.namelen = name->len;
+ v->value.func.args = args;
+
+ return v;
+}
+
static JsonPathParseItem *
makeItemList(List *list)
{
@@ -431,8 +596,17 @@ makeItemList(List *list)
{
JsonPathParseItem *c = (JsonPathParseItem *) lfirst(cell);
- end->next = c;
- end = c;
+ if (c->type == jpiMethod)
+ {
+ c->value.func.args = lcons(head, c->value.func.args);
+ head = c;
+ end = c;
+ }
+ else
+ {
+ end->next = c;
+ end = c;
+ }
}
return head;
@@ -510,6 +684,14 @@ makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern,
v->value.like_regex.flags |= JSP_REGEX_WSPACE;
cflags |= REG_EXPANDED;
break;
+ case 'q':
+ v->value.like_regex.flags |= JSP_REGEX_QUOTE;
+ if (!(v->value.like_regex.flags & (JSP_REGEX_MLINE | JSP_REGEX_SLINE | JSP_REGEX_WSPACE)))
+ {
+ cflags &= ~REG_ADVANCED;
+ cflags |= REG_QUOTE;
+ }
+ break;
default:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -528,6 +710,77 @@ makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern,
return v;
}
+static JsonPathParseItem *
+makeItemSequence(List *elems)
+{
+ JsonPathParseItem *v = makeItemType(jpiSequence);
+
+ v->value.sequence.elems = elems;
+
+ return v;
+}
+
+static JsonPathParseItem *
+makeItemObject(List *fields)
+{
+ JsonPathParseItem *v = makeItemType(jpiObject);
+
+ v->value.object.fields = fields;
+
+ return v;
+}
+
+static JsonPathParseItem *
+makeItemLambda(List *params, JsonPathParseItem *expr)
+{
+ JsonPathParseItem *v = makeItemType(jpiLambda);
+ ListCell *lc;
+
+ v->value.lambda.params = params;
+ v->value.lambda.expr = expr;
+
+ foreach(lc, params)
+ {
+ JsonPathParseItem *param1 = lfirst(lc);
+ ListCell *lc2;
+
+ if (param1->type != jpiArgument || param1->next)
+ yyerror(NULL, "lambda arguments must be identifiers");
+
+ foreach(lc2, params)
+ {
+ JsonPathParseItem *param2 = lfirst(lc2);
+
+ if (lc != lc2 &&
+ param1->value.string.len == param2->value.string.len &&
+ !strncmp(param1->value.string.val,
+ param2->value.string.val,
+ param1->value.string.len))
+ yyerror(NULL, "lambda argument names must be unique");
+ }
+ }
+
+ return v;
+}
+
+static JsonPathParseItem *
+setItemOutPathMode(JsonPathParseItem *jpi)
+{
+ jpi->flags |= JSPI_OUT_PATH;
+ return jpi;
+}
+
+static List *
+setItemsOutPathMode(List *items)
+{
+ ListCell *cell;
+
+ foreach(cell, items)
+ setItemOutPathMode(lfirst(cell));
+
+ return items;
+}
+
/*
* jsonpath_scan.l is compiled as part of jsonpath_gram.y. Currently, this is
* unavoidable because jsonpath_gram does not create a .h file to export its
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index 2165ffcc25..f249a7e051 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -63,22 +63,23 @@ fprintf_to_ereport(const char *fmt, const char *msg)
* quoted variable names and C-tyle comments.
* Exclusive states:
* - quoted strings
- * - non-quoted strings
* - quoted variable names
* - single-quoted strings
* - C-style comment
*/
%x xq
-%x xnq
%x xvq
%x xsq
%x xc
-special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
-any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f]
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\~\`\;]
+id_start [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\~\`\;\"\' \t\n\r\f(0-9)]
blank [ \t\n\r\f]
+id_rest ({id_start}|[0-9])
+id {id_start}{id_rest}*
+
digit [0-9]
integer (0|[1-9]{digit}*)
decimal {integer}\.{digit}+
@@ -95,66 +96,37 @@ hex_fail \\x{hex_dig}{0,1}
%%
-{any}+ {
- addstring(false, yytext, yyleng);
- }
-
-{blank}+ {
- yylval->str = scanstring;
- BEGIN INITIAL;
- return checkKeyword();
- }
+\\[\"\'\\] { addchar(false, yytext[1]); }
+\\b { addchar(false, '\b'); }
-\/\* {
- yylval->str = scanstring;
- BEGIN xc;
- }
+\\f { addchar(false, '\f'); }
-({special}|\"|\') {
- yylval->str = scanstring;
- yyless(0);
- BEGIN INITIAL;
- return checkKeyword();
- }
+\\n { addchar(false, '\n'); }
-<> {
- yylval->str = scanstring;
- BEGIN INITIAL;
- return checkKeyword();
- }
+\\r { addchar(false, '\r'); }
-\\[\"\'\\] { addchar(false, yytext[1]); }
+\\t { addchar(false, '\t'); }
-\\b { addchar(false, '\b'); }
+\\v { addchar(false, '\v'); }
-\\f { addchar(false, '\f'); }
+{unicode}+ { parseUnicode(yytext, yyleng); }
-\\n { addchar(false, '\n'); }
+{hex_char} { parseHexChar(yytext); }
-\\r { addchar(false, '\r'); }
+{unicode}*{unicodefail} { yyerror(NULL, "invalid unicode sequence"); }
-\\t { addchar(false, '\t'); }
+{hex_fail} { yyerror(NULL, "invalid hex character sequence"); }
-\\v { addchar(false, '\v'); }
-
-{unicode}+ { parseUnicode(yytext, yyleng); }
-
-{hex_char} { parseHexChar(yytext); }
-
-{unicode}*{unicodefail} { yyerror(NULL, "invalid unicode sequence"); }
-
-{hex_fail} { yyerror(NULL, "invalid hex character sequence"); }
-
-{unicode}+\\ {
+{unicode}+\\ {
/* throw back the \\, and treat as unicode */
yyless(yyleng - 1);
parseUnicode(yytext, yyleng);
}
-\\. { yyerror(NULL, "escape sequence is invalid"); }
+\\. { yyerror(NULL, "escape sequence is invalid"); }
-\\ { yyerror(NULL, "unexpected end after backslash"); }
+\\ { yyerror(NULL, "unexpected end after backslash"); }
<> { yyerror(NULL, "unexpected end of quoted string"); }
@@ -210,7 +182,9 @@ hex_fail \\x{hex_dig}{0,1}
\> { return GREATER_P; }
-\${any}+ {
+\=\> { return ARROW_P; }
+
+\$({id}|{integer}) {
addstring(true, yytext + 1, yyleng - 1);
addchar(false, '\0');
yylval->str = scanstring;
@@ -222,6 +196,13 @@ hex_fail \\x{hex_dig}{0,1}
BEGIN xvq;
}
+\@[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return CURRENT_P;
+ }
+
{special} { return *yytext; }
{blank}+ { /* ignore */ }
@@ -263,9 +244,11 @@ hex_fail \\x{hex_dig}{0,1}
({realfail1}|{realfail2}) { yyerror(NULL, "invalid floating point number"); }
-{any}+ {
+{id} {
addstring(true, yytext, yyleng);
- BEGIN xnq;
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return checkKeyword();
}
\" {
@@ -278,12 +261,6 @@ hex_fail \\x{hex_dig}{0,1}
BEGIN xsq;
}
-\\ {
- yyless(0);
- addchar(true, '\0');
- BEGIN xnq;
- }
-
<> { yyterminate(); }
%%
@@ -340,6 +317,7 @@ static const JsonPathKeyword keywords[] = {
{ 6, false, STRICT_P, "strict"},
{ 7, false, CEILING_P, "ceiling"},
{ 7, false, UNKNOWN_P, "unknown"},
+ { 8, false, DATETIME_P, "datetime"},
{ 8, false, KEYVALUE_P, "keyvalue"},
{ 10,false, LIKE_REGEX_P, "like_regex"},
};
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 5c886cfe96..184b918055 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -419,3 +419,4 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(opaque);
PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement);
PSEUDOTYPE_DUMMY_IO_FUNCS(anynonarray);
PSEUDOTYPE_DUMMY_IO_FUNCS(table_am_handler);
+PSEUDOTYPE_DUMMY_IO_FUNCS(jsonpath_fcxt);
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b9fdd99db8..f73c60ca88 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -469,6 +469,10 @@ static void add_cast_to(StringInfo buf, Oid typid);
static char *generate_qualified_type_name(Oid typid);
static text *string_to_text(char *str);
static char *flatten_reloptions(Oid relid);
+static void get_json_path_spec(Node *path_spec, deparse_context *context,
+ bool showimplicit);
+static void get_json_table_columns(TableFunc *tf, JsonTableParentNode *node,
+ deparse_context *context, bool showimplicit);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@@ -7538,6 +7542,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_Aggref:
case T_WindowFunc:
case T_FuncExpr:
+ case T_JsonExpr:
/* function-like: name(..) or name[..] */
return true;
@@ -7639,8 +7644,10 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
CoercionForm type = ((FuncExpr *) parentNode)->funcformat;
if (type == COERCE_EXPLICIT_CAST ||
- type == COERCE_IMPLICIT_CAST)
+ type == COERCE_IMPLICIT_CAST ||
+ type == COERCE_INTERNAL_CAST)
return false;
+
return true; /* own parentheses */
}
case T_BoolExpr: /* lower precedence */
@@ -7654,6 +7661,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;
@@ -7690,7 +7698,8 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
CoercionForm type = ((FuncExpr *) parentNode)->funcformat;
if (type == COERCE_EXPLICIT_CAST ||
- type == COERCE_IMPLICIT_CAST)
+ type == COERCE_IMPLICIT_CAST ||
+ type == COERCE_INTERNAL_CAST)
return false;
return true; /* own parentheses */
}
@@ -7815,6 +7824,128 @@ get_rule_expr_paren(Node *node, deparse_context *context,
}
+/*
+ * get_json_path_spec - Parse back a JSON path specification
+ */
+static void
+get_json_path_spec(Node *path_spec, deparse_context *context, bool showimplicit)
+{
+ if (IsA(path_spec, Const))
+ get_const_expr((Const *) path_spec, context, -1);
+ else
+ get_rule_expr(path_spec, context, showimplicit);
+}
+
+/*
+ * get_json_format - Parse back a JsonFormat structure
+ */
+static void
+get_json_format(JsonFormat *format, deparse_context *context)
+{
+ if (format->type == JS_FORMAT_DEFAULT)
+ return;
+
+ appendStringInfoString(context->buf,
+ 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.type !=
+ (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON))
+ get_json_format(&returning->format, context);
+}
+
+/*
+ * get_coercion - Parse back a coercion
+ */
+static void
+get_coercion(Expr *arg, deparse_context *context, bool showimplicit,
+ Node *node, CoercionForm format, Oid typid, int32 typmod)
+{
+ if (format == COERCE_INTERNAL_CAST ||
+ (format == COERCE_IMPLICIT_CAST && !showimplicit))
+ {
+ /* don't show the implicit cast */
+ get_rule_expr_paren((Node *) arg, context, false, node);
+ }
+ else
+ {
+ get_coercion_expr((Node *) arg, context, typid, typmod, node);
+ }
+}
+
+static void
+get_json_behavior(JsonBehavior *behavior, deparse_context *context,
+ const char *on)
+{
+ switch (behavior->btype)
+ {
+ case JSON_BEHAVIOR_DEFAULT:
+ appendStringInfoString(context->buf, " DEFAULT ");
+ get_rule_expr(behavior->default_expr, context, false);
+ break;
+
+ case JSON_BEHAVIOR_EMPTY:
+ appendStringInfoString(context->buf, " EMPTY");
+ break;
+
+ case JSON_BEHAVIOR_EMPTY_ARRAY:
+ appendStringInfoString(context->buf, " EMPTY ARRAY");
+ break;
+
+ case JSON_BEHAVIOR_EMPTY_OBJECT:
+ appendStringInfoString(context->buf, " EMPTY OBJECT");
+ break;
+
+ case JSON_BEHAVIOR_ERROR:
+ appendStringInfoString(context->buf, " ERROR");
+ break;
+
+ case JSON_BEHAVIOR_FALSE:
+ appendStringInfoString(context->buf, " FALSE");
+ break;
+
+ case JSON_BEHAVIOR_NULL:
+ appendStringInfoString(context->buf, " NULL");
+ break;
+
+ case JSON_BEHAVIOR_TRUE:
+ appendStringInfoString(context->buf, " TRUE");
+ break;
+
+ case JSON_BEHAVIOR_UNKNOWN:
+ appendStringInfoString(context->buf, " UNKNOWN");
+ break;
+ }
+
+ appendStringInfo(context->buf, " ON %s", on);
+}
+
+
/* ----------
* get_rule_expr - Parse back an expression
*
@@ -8197,83 +8328,38 @@ get_rule_expr(Node *node, deparse_context *context,
case T_RelabelType:
{
RelabelType *relabel = (RelabelType *) node;
- Node *arg = (Node *) relabel->arg;
- if (relabel->relabelformat == COERCE_IMPLICIT_CAST &&
- !showimplicit)
- {
- /* don't show the implicit cast */
- get_rule_expr_paren(arg, context, false, node);
- }
- else
- {
- get_coercion_expr(arg, context,
- relabel->resulttype,
- relabel->resulttypmod,
- node);
- }
+ get_coercion(relabel->arg, context, showimplicit, node,
+ relabel->relabelformat, relabel->resulttype,
+ relabel->resulttypmod);
}
break;
case T_CoerceViaIO:
{
CoerceViaIO *iocoerce = (CoerceViaIO *) node;
- Node *arg = (Node *) iocoerce->arg;
- if (iocoerce->coerceformat == COERCE_IMPLICIT_CAST &&
- !showimplicit)
- {
- /* don't show the implicit cast */
- get_rule_expr_paren(arg, context, false, node);
- }
- else
- {
- get_coercion_expr(arg, context,
- iocoerce->resulttype,
- -1,
- node);
- }
+ get_coercion(iocoerce->arg, context, showimplicit, node,
+ iocoerce->coerceformat, iocoerce->resulttype, -1);
}
break;
case T_ArrayCoerceExpr:
{
ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
- Node *arg = (Node *) acoerce->arg;
- if (acoerce->coerceformat == COERCE_IMPLICIT_CAST &&
- !showimplicit)
- {
- /* don't show the implicit cast */
- get_rule_expr_paren(arg, context, false, node);
- }
- else
- {
- get_coercion_expr(arg, context,
- acoerce->resulttype,
- acoerce->resulttypmod,
- node);
- }
+ get_coercion(acoerce->arg, context, showimplicit, node,
+ acoerce->coerceformat, acoerce->resulttype,
+ acoerce->resulttypmod);
}
break;
case T_ConvertRowtypeExpr:
{
ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
- Node *arg = (Node *) convert->arg;
- if (convert->convertformat == COERCE_IMPLICIT_CAST &&
- !showimplicit)
- {
- /* don't show the implicit cast */
- get_rule_expr_paren(arg, context, false, node);
- }
- else
- {
- get_coercion_expr(arg, context,
- convert->resulttype, -1,
- node);
- }
+ get_coercion(convert->arg, context, showimplicit, node,
+ convert->convertformat, convert->resulttype, -1);
}
break;
@@ -8826,21 +8912,10 @@ get_rule_expr(Node *node, deparse_context *context,
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
- Node *arg = (Node *) ctest->arg;
- if (ctest->coercionformat == COERCE_IMPLICIT_CAST &&
- !showimplicit)
- {
- /* don't show the implicit cast */
- get_rule_expr(arg, context, false);
- }
- else
- {
- get_coercion_expr(arg, context,
- ctest->resulttype,
- ctest->resulttypmod,
- node);
- }
+ get_coercion(ctest->arg, context, showimplicit, node,
+ ctest->coercionformat, ctest->resulttype,
+ ctest->resulttypmod);
}
break;
@@ -8987,6 +9062,86 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+
+ case T_JsonValueExpr:
+ {
+ JsonValueExpr *jve = (JsonValueExpr *) node;
+
+ get_rule_expr((Node *) jve->expr, context, false);
+ get_json_format(&jve->format, 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;
+ default:
+ elog(ERROR, "unexpected JsonExpr type: %d", jexpr->op);
+ break;
+ }
+
+ get_rule_expr(jexpr->raw_expr, context, showimplicit);
+
+ get_json_format(&jexpr->format, context);
+
+ 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",
+ ((Value *) lfirst(lc1))->val.str);
+ }
+ }
+
+ if (jexpr->op != IS_JSON_EXISTS)
+ get_json_returning(&jexpr->returning, context,
+ jexpr->op != IS_JSON_VALUE);
+
+ if (jexpr->wrapper == JSW_CONDITIONAL)
+ appendStringInfo(buf, " WITH CONDITIONAL WRAPPER");
+
+ if (jexpr->wrapper == JSW_UNCONDITIONAL)
+ appendStringInfo(buf, " WITH UNCONDITIONAL WRAPPER");
+
+ if (jexpr->omit_quotes)
+ appendStringInfo(buf, " OMIT QUOTES");
+
+ if (jexpr->op != IS_JSON_EXISTS)
+ get_json_behavior(&jexpr->on_empty, context, "EMPTY");
+
+ get_json_behavior(&jexpr->on_error, context, "ERROR");
+
+ appendStringInfoString(buf, ")");
+ }
+ break;
+
case T_List:
{
char *sep;
@@ -9083,6 +9238,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:
@@ -9153,6 +9309,76 @@ get_oper_expr(OpExpr *expr, deparse_context *context)
appendStringInfoChar(buf, ')');
}
+static void
+get_func_opts(FuncFormat aggformat, Node *aggformatopts, deparse_context *context)
+{
+ switch (aggformat)
+ {
+ case FUNCFMT_JSON_OBJECT:
+ case FUNCFMT_JSON_OBJECTAGG:
+ case FUNCFMT_JSON_ARRAY:
+ case FUNCFMT_JSON_ARRAYAGG:
+ {
+ JsonCtorOpts *opts = castNode(JsonCtorOpts, aggformatopts);
+
+ if (!opts)
+ break;
+
+ if (opts->absent_on_null)
+ {
+ if (aggformat == FUNCFMT_JSON_OBJECT ||
+ aggformat == FUNCFMT_JSON_OBJECTAGG)
+ appendStringInfoString(context->buf, " ABSENT ON NULL");
+ }
+ else
+ {
+ if (aggformat == FUNCFMT_JSON_ARRAY ||
+ aggformat == FUNCFMT_JSON_ARRAYAGG)
+ appendStringInfoString(context->buf, " NULL ON NULL");
+ }
+
+ if (opts->unique)
+ appendStringInfoString(context->buf, " WITH UNIQUE KEYS");
+
+ get_json_returning(&opts->returning, context, true);
+ }
+ break;
+
+ case FUNCFMT_IS_JSON:
+ {
+ JsonIsPredicateOpts *opts =
+ castNode(JsonIsPredicateOpts, aggformatopts);
+
+ appendStringInfoString(context->buf, " IS JSON");
+
+ if (!opts)
+ break;
+
+ switch (opts->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 (opts->unique_keys)
+ appendStringInfoString(context->buf, " WITH UNIQUE KEYS");
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
/*
* get_func_expr - Parse back a FuncExpr node
*/
@@ -9164,15 +9390,19 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
Oid funcoid = expr->funcid;
Oid argtypes[FUNC_MAX_ARGS];
int nargs;
+ int firstarg;
+ int lastarg = list_length(expr->args);
List *argnames;
bool use_variadic;
ListCell *l;
+ const char *funcname;
/*
* If the function call came from an implicit coercion, then just show the
* first argument --- unless caller wants to see implicit coercions.
*/
- if (expr->funcformat == COERCE_IMPLICIT_CAST && !showimplicit)
+ if (expr->funcformat == COERCE_INTERNAL_CAST ||
+ (expr->funcformat == COERCE_IMPLICIT_CAST && !showimplicit))
{
get_rule_expr_paren((Node *) linitial(expr->args), context,
false, (Node *) expr);
@@ -9220,22 +9450,68 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
nargs++;
}
- appendStringInfo(buf, "%s(",
- generate_function_name(funcoid, nargs,
- argnames, argtypes,
- expr->funcvariadic,
- &use_variadic,
- context->special_exprkind));
+ switch (expr->funcformat2)
+ {
+ case FUNCFMT_JSON_OBJECT:
+ funcname = "JSON_OBJECT";
+ firstarg = 2;
+ use_variadic = false;
+ break;
+
+ case FUNCFMT_JSON_ARRAY:
+ funcname = "JSON_ARRAY";
+ firstarg = 1;
+ use_variadic = false;
+ break;
+
+ case FUNCFMT_IS_JSON:
+ funcname = NULL;
+ firstarg = 0;
+ lastarg = 0;
+ use_variadic = false;
+ break;
+
+ default:
+ funcname = generate_function_name(funcoid, nargs,
+ argnames, argtypes,
+ expr->funcvariadic,
+ &use_variadic,
+ context->special_exprkind);
+ firstarg = 0;
+ break;
+ }
+
+ if (funcname)
+ appendStringInfo(buf, "%s(", funcname);
+ else if (!PRETTY_PAREN(context))
+ appendStringInfoChar(buf, '(');
+
nargs = 0;
foreach(l, expr->args)
{
- if (nargs++ > 0)
- appendStringInfoString(buf, ", ");
+ if (nargs > lastarg)
+ break;
+
+ if (nargs++ < firstarg)
+ continue;
+
+ if (nargs > firstarg + 1)
+ {
+ const char *sep = expr->funcformat2 == FUNCFMT_JSON_OBJECT &&
+ !((nargs - firstarg) % 2) ? " : " : ", ";
+
+ appendStringInfoString(buf, sep);
+ }
+
if (use_variadic && lnext(l) == NULL)
appendStringInfoString(buf, "VARIADIC ");
get_rule_expr((Node *) lfirst(l), context, true);
}
- appendStringInfoChar(buf, ')');
+
+ get_func_opts(expr->funcformat2, expr->funcformatopts, context);
+
+ if (funcname || !PRETTY_PAREN(context))
+ appendStringInfoChar(buf, ')');
}
/*
@@ -9247,8 +9523,9 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
{
StringInfo buf = context->buf;
Oid argtypes[FUNC_MAX_ARGS];
+ const char *funcname;
int nargs;
- bool use_variadic;
+ bool use_variadic = false;
/*
* For a combining aggregate, we look up and deparse the corresponding
@@ -9277,13 +9554,24 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
/* Extract the argument types as seen by the parser */
nargs = get_aggregate_argtypes(aggref, argtypes);
+ switch (aggref->aggformat)
+ {
+ case FUNCFMT_JSON_OBJECTAGG:
+ funcname = "JSON_OBJECTAGG";
+ break;
+ case FUNCFMT_JSON_ARRAYAGG:
+ funcname = "JSON_ARRAYAGG";
+ break;
+ default:
+ funcname = generate_function_name(aggref->aggfnoid, nargs, NIL,
+ argtypes, aggref->aggvariadic,
+ &use_variadic,
+ context->special_exprkind);
+ break;
+ }
+
/* 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))
@@ -9319,7 +9607,17 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
if (tle->resjunk)
continue;
if (i++ > 0)
- appendStringInfoString(buf, ", ");
+ {
+ if (aggref->aggformat == FUNCFMT_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);
@@ -9333,6 +9631,8 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
}
}
+ get_func_opts(aggref->aggformat, aggref->aggformatopts, context);
+
if (aggref->aggfilter != NULL)
{
appendStringInfoString(buf, ") FILTER (WHERE ");
@@ -9371,6 +9671,7 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
int nargs;
List *argnames;
ListCell *l;
+ const char *funcname;
if (list_length(wfunc->args) > FUNC_MAX_ARGS)
ereport(ERROR,
@@ -9388,16 +9689,39 @@ 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));
+ switch (wfunc->winformat)
+ {
+ case FUNCFMT_JSON_OBJECTAGG:
+ funcname = "JSON_OBJECTAGG";
+ break;
+ case FUNCFMT_JSON_ARRAYAGG:
+ funcname = "JSON_ARRAYAGG";
+ break;
+ default:
+ funcname = generate_function_name(wfunc->winfnoid, nargs, argnames,
+ argtypes, false, NULL,
+ context->special_exprkind);
+ break;
+ }
+
+ 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 (wfunc->winformat == FUNCFMT_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);
+ }
+
+ get_func_opts(wfunc->winformat, wfunc->winformatopts, context);
if (wfunc->aggfilter != NULL)
{
@@ -9795,16 +10119,14 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
/* ----------
- * get_tablefunc - Parse back a table function
+ * get_xmltable - Parse back a XMLTABLE function
* ----------
*/
static void
-get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
+get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit)
{
StringInfo buf = context->buf;
- /* XMLTABLE is the only existing implementation. */
-
appendStringInfoString(buf, "XMLTABLE(");
if (tf->ns_uris != NIL)
@@ -9895,6 +10217,280 @@ get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
appendStringInfoChar(buf, ')');
}
+/*
+ * get_json_nested_columns - Parse back nested JSON_TABLE columns
+ */
+static void
+get_json_table_nested_columns(TableFunc *tf, Node *node,
+ deparse_context *context, bool showimplicit,
+ bool needcomma)
+{
+ if (IsA(node, JsonTableSiblingNode))
+ {
+ JsonTableSiblingNode *n = (JsonTableSiblingNode *) node;
+
+ get_json_table_nested_columns(tf, n->larg, context, showimplicit,
+ needcomma);
+ get_json_table_nested_columns(tf, n->rarg, context, showimplicit, true);
+ }
+ else
+ {
+ JsonTableParentNode *n = castNode(JsonTableParentNode, node);
+
+ if (needcomma)
+ appendStringInfoChar(context->buf, ',');
+
+ appendStringInfoChar(context->buf, ' ');
+ appendContextKeyword(context, "NESTED PATH ", 0, 0, 0);
+ get_const_expr(n->path, context, -1);
+ appendStringInfo(context->buf, " AS %s", quote_identifier(n->name));
+ get_json_table_columns(tf, n, context, showimplicit);
+ }
+}
+
+/*
+ * get_json_table_plan - Parse back a JSON_TABLE plan
+ */
+static void
+get_json_table_plan(TableFunc *tf, Node *node, deparse_context *context,
+ bool parenthesize)
+{
+ if (parenthesize)
+ appendStringInfoChar(context->buf, '(');
+
+ if (IsA(node, JsonTableSiblingNode))
+ {
+ JsonTableSiblingNode *n = (JsonTableSiblingNode *) node;
+
+ get_json_table_plan(tf, n->larg, context,
+ IsA(n->larg, JsonTableSiblingNode) ||
+ castNode(JsonTableParentNode, n->larg)->child);
+
+ appendStringInfoString(context->buf, n->cross ? " CROSS " : " UNION ");
+
+ get_json_table_plan(tf, n->rarg, context,
+ IsA(n->rarg, JsonTableSiblingNode) ||
+ castNode(JsonTableParentNode, n->rarg)->child);
+ }
+ else
+ {
+ JsonTableParentNode *n = castNode(JsonTableParentNode, node);
+
+ appendStringInfoString(context->buf, quote_identifier(n->name));
+
+ if (n->child)
+ {
+ appendStringInfoString(context->buf,
+ n->outerJoin ? " OUTER " : " INNER ");
+ get_json_table_plan(tf, n->child, context,
+ IsA(n->child, JsonTableSiblingNode));
+ }
+ }
+
+ if (parenthesize)
+ appendStringInfoChar(context->buf, ')');
+}
+
+/*
+ * get_json_table_columns - Parse back JSON_TABLE columns
+ */
+static void
+get_json_table_columns(TableFunc *tf, JsonTableParentNode *node,
+ deparse_context *context, bool showimplicit)
+{
+ StringInfo buf = context->buf;
+ ListCell *l1;
+ ListCell *l2;
+ ListCell *l3;
+ ListCell *l4;
+ int colnum = 0;
+
+ l2 = list_head(tf->coltypes);
+ l3 = list_head(tf->coltypmods);
+ l4 = list_head(tf->colvalexprs);
+
+ appendStringInfoChar(buf, ' ');
+ appendContextKeyword(context, "COLUMNS (", 0, 0, 0);
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel += PRETTYINDENT_VAR;
+
+ foreach(l1, tf->colnames)
+ {
+ char *colname = strVal(lfirst(l1));
+ JsonExpr *colexpr;
+ Oid typid;
+ int32 typmod;
+ bool ordinality;
+
+ typid = lfirst_oid(l2);
+ l2 = lnext(l2);
+ typmod = lfirst_int(l3);
+ l3 = lnext(l3);
+ colexpr = castNode(JsonExpr, lfirst(l4));
+ l4 = lnext(l4);
+
+ if (colnum < node->colMin)
+ {
+ colnum++;
+ continue;
+ }
+
+ if (colnum > node->colMax)
+ break;
+
+ if (colnum > node->colMin)
+ appendStringInfoString(buf, ", ");
+
+ colnum++;
+
+ ordinality = !colexpr;
+
+ appendContextKeyword(context, "", 0, 0, 0);
+
+ appendStringInfo(buf, "%s %s", quote_identifier(colname),
+ ordinality ? "FOR ORDINALITY" :
+ format_type_with_typemod(typid, typmod));
+ if (ordinality)
+ continue;
+
+ if (colexpr->op == IS_JSON_QUERY)
+ appendStringInfoString(buf,
+ colexpr->format.type == JS_FORMAT_JSONB ?
+ " FORMAT JSONB" : " FORMAT JSON");
+
+ appendStringInfoString(buf, " PATH ");
+
+ get_json_path_spec(colexpr->path_spec, context, showimplicit);
+
+ if (colexpr->wrapper == JSW_CONDITIONAL)
+ appendStringInfo(buf, " WITH CONDITIONAL WRAPPER");
+
+ if (colexpr->wrapper == JSW_UNCONDITIONAL)
+ appendStringInfo(buf, " WITH UNCONDITIONAL WRAPPER");
+
+ if (colexpr->omit_quotes)
+ appendStringInfo(buf, " OMIT QUOTES");
+
+ if (colexpr->on_empty.btype != JSON_BEHAVIOR_NULL)
+ get_json_behavior(&colexpr->on_empty, context, "EMPTY");
+
+ if (colexpr->on_error.btype != JSON_BEHAVIOR_NULL)
+ get_json_behavior(&colexpr->on_error, context, "ERROR");
+ }
+
+ if (node->child)
+ get_json_table_nested_columns(tf, node->child, context, showimplicit,
+ node->colMax >= node->colMin);
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel -= PRETTYINDENT_VAR;
+
+ appendContextKeyword(context, ")", 0, 0, 0);
+}
+
+/* ----------
+ * get_json_table - Parse back a JSON_TABLE function
+ * ----------
+ */
+static void
+get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
+{
+ StringInfo buf = context->buf;
+ JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
+ JsonTableParentNode *root = castNode(JsonTableParentNode, tf->plan);
+
+ appendStringInfoString(buf, "JSON_TABLE(");
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel += PRETTYINDENT_VAR;
+
+ appendContextKeyword(context, "", 0, 0, 0);
+
+ get_rule_expr(jexpr->raw_expr, context, showimplicit);
+
+ if (jexpr->format.type != JS_FORMAT_DEFAULT)
+ {
+ appendStringInfoString(buf,
+ jexpr->format.type == JS_FORMAT_JSONB ?
+ " FORMAT JSONB" : " FORMAT JSON");
+
+ if (jexpr->format.encoding != JS_ENC_DEFAULT)
+ {
+ const char *encoding =
+ jexpr->format.encoding == JS_ENC_UTF16 ? "UTF16" :
+ jexpr->format.encoding == JS_ENC_UTF32 ? "UTF32" :
+ "UTF8";
+
+ appendStringInfo(buf, " ENCODING %s", encoding);
+ }
+ }
+
+ appendStringInfoString(buf, ", ");
+
+ get_const_expr(root->path, context, -1);
+
+ appendStringInfo(buf, " AS %s", quote_identifier(root->name));
+
+ if (jexpr->passing.values)
+ {
+ ListCell *lc1, *lc2;
+ bool needcomma = false;
+
+ appendStringInfoChar(buf, ' ');
+ appendContextKeyword(context, "PASSING ", 0, 0, 0);
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel += PRETTYINDENT_VAR;
+
+ forboth(lc1, jexpr->passing.names,
+ lc2, jexpr->passing.values)
+ {
+ if (needcomma)
+ appendStringInfoString(buf, ", ");
+ needcomma = true;
+
+ appendContextKeyword(context, "", 0, 0, 0);
+
+ get_rule_expr((Node *) lfirst(lc2), context, false);
+ appendStringInfo(buf, " AS %s",
+ quote_identifier(((Value *) lfirst(lc1))->val.str));
+ }
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel -= PRETTYINDENT_VAR;
+ }
+
+ get_json_table_columns(tf, root, context, showimplicit);
+
+ appendStringInfoChar(buf, ' ');
+ appendContextKeyword(context, "PLAN ", 0, 0, 0);
+ get_json_table_plan(tf, (Node *) root, context, true);
+
+ if (jexpr->on_error.btype != JSON_BEHAVIOR_EMPTY)
+ get_json_behavior(&jexpr->on_error, context, "ERROR");
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel -= PRETTYINDENT_VAR;
+
+ appendContextKeyword(context, ")", 0, 0, 0);
+}
+
+/* ----------
+ * get_tablefunc - Parse back a table function
+ * ----------
+ */
+static void
+get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
+{
+ /* XMLTABLE and JSON_TABLE are the only existing implementations. */
+
+ if (tf->functype == TFT_XMLTABLE)
+ get_xmltable(tf, context, showimplicit);
+ else if (tf->functype == TFT_JSON_TABLE)
+ get_json_table(tf, context, showimplicit);
+}
+
/* ----------
* get_from_clause - Parse back a FROM clause
*
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index e5ac371fa0..c2ffd680b9 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -70,7 +70,6 @@ typedef struct
static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
static Timestamp dt2local(Timestamp dt, int timezone);
-static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
static void AdjustIntervalForTypmod(Interval *interval, int32 typmod);
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
@@ -338,11 +337,11 @@ timestamp_scale(PG_FUNCTION_ARGS)
}
/*
- * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
+ * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod
* Works for either timestamp or timestamptz.
*/
-static void
-AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+bool
+AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
{
static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
INT64CONST(1000000),
@@ -368,10 +367,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
&& (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
{
if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
+ {
+ if (error)
+ {
+ *error = true;
+ return false;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("timestamp(%d) precision must be between %d and %d",
typmod, 0, MAX_TIMESTAMP_PRECISION)));
+ }
if (*time >= INT64CONST(0))
{
@@ -384,8 +391,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
* TimestampScales[typmod]);
}
}
+
+ return true;
}
+void
+AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+{
+ (void) AdjustTimestampForTypmodError(time, typmod, NULL);
+}
/* timestamptz_in()
* Convert a string to internal form.
@@ -714,21 +728,25 @@ make_timestamptz_at_timezone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMPTZ(result);
}
-/*
- * to_timestamp(double precision)
- * Convert UNIX epoch to timestamptz.
- */
-Datum
-float8_timestamptz(PG_FUNCTION_ARGS)
+TimestampTz
+float8_timestamptz_internal(float8 seconds, bool *error)
{
- float8 seconds = PG_GETARG_FLOAT8(0);
+ float8 saved_seconds = seconds;
TimestampTz result;
/* Deal with NaN and infinite inputs ... */
if (isnan(seconds))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp cannot be NaN")));
+ }
if (isinf(seconds))
{
@@ -744,9 +762,17 @@ float8_timestamptz(PG_FUNCTION_ARGS)
(float8) SECS_PER_DAY * (DATETIME_MIN_JULIAN - UNIX_EPOCH_JDATE)
|| seconds >=
(float8) SECS_PER_DAY * (TIMESTAMP_END_JULIAN - UNIX_EPOCH_JDATE))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range: \"%g\"", seconds)));
+ }
/* Convert UNIX epoch to Postgres epoch */
seconds -= ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
@@ -756,13 +782,32 @@ float8_timestamptz(PG_FUNCTION_ARGS)
/* Recheck in case roundoff produces something just out of range */
if (!IS_VALID_TIMESTAMP(result))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range: \"%g\"",
- PG_GETARG_FLOAT8(0))));
+ errmsg("timestamp out of range: \"%g\"", saved_seconds)));
+ }
}
- PG_RETURN_TIMESTAMP(result);
+ return result;
+}
+
+/*
+ * to_timestamp(double precision)
+ * Convert UNIX epoch to timestamptz.
+ */
+Datum
+float8_timestamptz(PG_FUNCTION_ARGS)
+{
+ float8 seconds = PG_GETARG_FLOAT8(0);
+
+ PG_RETURN_TIMESTAMP(float8_timestamptz_internal(seconds, NULL));
}
/* timestamptz_out()
@@ -5195,8 +5240,8 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
}
-static TimestampTz
-timestamp2timestamptz(Timestamp timestamp)
+TimestampTz
+timestamp2timestamptz_internal(Timestamp timestamp, int *tzp, bool *error)
{
TimestampTz result;
struct pg_tm tt,
@@ -5205,23 +5250,30 @@ timestamp2timestamptz(Timestamp timestamp)
int tz;
if (TIMESTAMP_NOT_FINITE(timestamp))
- result = timestamp;
- else
- {
- if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ return timestamp;
- tz = DetermineTimeZoneOffset(tm, session_timezone);
+ if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+ {
+ tz = tzp ? *tzp : DetermineTimeZoneOffset(tm, session_timezone);
- if (tm2timestamp(tm, fsec, &tz, &result) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ if (!tm2timestamp(tm, fsec, &tz, &result))
+ return result;
}
- return result;
+ if (error)
+ *error = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ return 0;
+}
+
+static TimestampTz
+timestamp2timestamptz(Timestamp timestamp)
+{
+ return timestamp2timestamptz_internal(timestamp, NULL, NULL);
}
/* timestamptz_timestamp()
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 16f5ca233a..72876533ac 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -207,6 +207,7 @@ Section: Class 22 - Data Exception
2200S E ERRCODE_INVALID_XML_COMMENT invalid_xml_comment
2200T E ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION invalid_xml_processing_instruction
22030 E ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE duplicate_json_object_key_value
+22031 E ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION invalid_argument_for_json_datetime_function
22032 E ERRCODE_INVALID_JSON_TEXT invalid_json_text
22033 E ERRCODE_INVALID_JSON_SUBSCRIPT invalid_json_subscript
22034 E ERRCODE_MORE_THAN_ONE_JSON_ITEM more_than_one_json_item
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index b7fac5d295..d6bcc27be4 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -50,7 +50,7 @@ static TypeFuncClass get_type_func_class(Oid typid, Oid *base_typeid);
* and error checking
*/
FuncCallContext *
-init_MultiFuncCall(PG_FUNCTION_ARGS)
+init_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext **pfuncctx)
{
FuncCallContext *retval;
@@ -62,7 +62,7 @@ init_MultiFuncCall(PG_FUNCTION_ARGS)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
- if (fcinfo->flinfo->fn_extra == NULL)
+ if (*pfuncctx == NULL)
{
/*
* First call
@@ -97,7 +97,7 @@ init_MultiFuncCall(PG_FUNCTION_ARGS)
/*
* save the pointer for cross-call use
*/
- fcinfo->flinfo->fn_extra = retval;
+ *pfuncctx = retval;
/*
* Ensure we will get shut down cleanly if the exprcontext is not run
@@ -105,7 +105,7 @@ init_MultiFuncCall(PG_FUNCTION_ARGS)
*/
RegisterExprContextCallback(rsi->econtext,
shutdown_MultiFuncCall,
- PointerGetDatum(fcinfo->flinfo));
+ PointerGetDatum(pfuncctx));
}
else
{
@@ -125,9 +125,9 @@ init_MultiFuncCall(PG_FUNCTION_ARGS)
* Do Multi-function per-call setup
*/
FuncCallContext *
-per_MultiFuncCall(PG_FUNCTION_ARGS)
+per_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext **pfuncctx)
{
- FuncCallContext *retval = (FuncCallContext *) fcinfo->flinfo->fn_extra;
+ FuncCallContext *retval = *pfuncctx;
return retval;
}
@@ -137,17 +137,18 @@ per_MultiFuncCall(PG_FUNCTION_ARGS)
* Clean up after init_MultiFuncCall
*/
void
-end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx)
+end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx,
+ FuncCallContext **pfuncctx)
{
ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
/* Deregister the shutdown callback */
UnregisterExprContextCallback(rsi->econtext,
shutdown_MultiFuncCall,
- PointerGetDatum(fcinfo->flinfo));
+ PointerGetDatum(pfuncctx));
/* But use it to do the real work */
- shutdown_MultiFuncCall(PointerGetDatum(fcinfo->flinfo));
+ shutdown_MultiFuncCall(PointerGetDatum(pfuncctx));
}
/*
@@ -157,11 +158,11 @@ end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx)
static void
shutdown_MultiFuncCall(Datum arg)
{
- FmgrInfo *flinfo = (FmgrInfo *) DatumGetPointer(arg);
- FuncCallContext *funcctx = (FuncCallContext *) flinfo->fn_extra;
+ FuncCallContext **pfuncctx = (FuncCallContext **) DatumGetPointer(arg);
+ FuncCallContext *funcctx = *pfuncctx;
/* unbind from flinfo */
- flinfo->fn_extra = NULL;
+ *pfuncctx = NULL;
/*
* Delete context that holds all multi-call data, including the
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 044695a046..88b42b47dc 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -535,14 +535,22 @@
# 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_objectagg', aggtransfn => 'json_objectagg_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_objectagg', aggtransfn => 'jsonb_objectagg_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_cast.dat b/src/include/catalog/pg_cast.dat
index aabfa7af03..f19ef807ca 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -496,6 +496,13 @@
{ castsource => 'jsonb', casttarget => 'json', castfunc => '0',
castcontext => 'a', castmethod => 'i' },
+# jsonb to/from bytea
+{ castsource => 'jsonb', casttarget => 'bytea', castfunc => '0',
+ castcontext => 'e', castmethod => 'b' },
+{ castsource => 'bytea', casttarget => 'jsonb',
+ castfunc => 'jsonb_from_bytea(bytea)',
+ castcontext => 'e', castmethod => 'f' },
+
# jsonb to numeric and bool types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
castcontext => 'e', castmethod => 'f' },
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index bacafa5183..83e12f1d00 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3263,5 +3263,94 @@
oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
oprrest => 'contsel', oprjoin => 'contjoinsel' },
+{ oid => '6071', descr => 'jsonpath exists',
+ oprname => '@?', oprleft => 'json', oprright => 'jsonpath',
+ oprresult => 'bool', oprcode => 'json_path_exists(json,jsonpath)',
+ oprrest => 'contsel', oprjoin => 'contjoinsel' },
+{ oid => '6108', descr => 'jsonpath predicate',
+ oprname => '@@', oprleft => 'json', oprright => 'jsonpath',
+ oprresult => 'bool', oprcode => 'json_path_match(json,jsonpath)',
+ oprrest => 'contsel', oprjoin => 'contjoinsel' },
+
+{ oid => '6017', descr => 'jsonpath == jsonpath',
+ oprname => '==', oprleft => 'jsonpath', oprright => 'jsonpath',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_eq_jsonpath(jsonpath,jsonpath)' },
+{ oid => '6018', descr => 'jsonpath != jsonpath',
+ oprname => '!=', oprleft => 'jsonpath', oprright => 'jsonpath',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_ne_jsonpath(jsonpath,jsonpath)' },
+{ oid => '6019', descr => 'jsonpath < jsonpath',
+ oprname => '<', oprleft => 'jsonpath', oprright => 'jsonpath',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_lt_jsonpath(jsonpath,jsonpath)' },
+{ oid => '6020', descr => 'jsonpath <= jsonpath',
+ oprname => '<=', oprleft => 'jsonpath', oprright => 'jsonpath',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_le_jsonpath(jsonpath,jsonpath)' },
+{ oid => '6021', descr => 'jsonpath > jsonpath',
+ oprname => '>', oprleft => 'jsonpath', oprright => 'jsonpath',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_gt_jsonpath(jsonpath,jsonpath)' },
+{ oid => '6022', descr => 'jsonpath >= jsonpath',
+ oprname => '>=', oprleft => 'jsonpath', oprright => 'jsonpath',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_ge_jsonpath(jsonpath,jsonpath)' },
+{ oid => '6023', descr => 'jsonpath + jsonpath',
+ oprname => '+', oprleft => 'jsonpath', oprright => 'jsonpath',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_pl_jsonpath(jsonpath,jsonpath)' },
+{ oid => '6024', descr => 'jsonpath - jsonpath',
+ oprname => '-', oprleft => 'jsonpath', oprright => 'jsonpath',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_mi_jsonpath(jsonpath,jsonpath)' },
+{ oid => '6025', descr => 'jsonpath * jsonpath',
+ oprname => '*', oprleft => 'jsonpath', oprright => 'jsonpath',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_mul_jsonpath(jsonpath,jsonpath)' },
+{ oid => '6026', descr => 'jsonpath / jsonpath',
+ oprname => '/', oprleft => 'jsonpath', oprright => 'jsonpath',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_div_jsonpath(jsonpath,jsonpath)' },
+{ oid => '6027', descr => 'jsonpath % jsonpath',
+ oprname => '%', oprleft => 'jsonpath', oprright => 'jsonpath',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_mod_jsonpath(jsonpath,jsonpath)' },
+
+{ oid => '6029', descr => 'jsonpath == jsonb',
+ oprname => '==', oprleft => 'jsonpath', oprright => 'jsonb',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_eq_jsonb(jsonpath,jsonb)' },
+{ oid => '6030', descr => 'jsonpath != jsonb',
+ oprname => '!=', oprleft => 'jsonpath', oprright => 'jsonb',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_ne_jsonb(jsonpath,jsonb)' },
+{ oid => '6031', descr => 'jsonpath < jsonb',
+ oprname => '<', oprleft => 'jsonpath', oprright => 'jsonb',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_lt_jsonb(jsonpath,jsonb)' },
+{ oid => '6032', descr => 'jsonpath <= jsonb',
+ oprname => '<=', oprleft => 'jsonpath', oprright => 'jsonb',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_le_jsonb(jsonpath,jsonb)' },
+{ oid => '6033', descr => 'jsonpath > jsonb',
+ oprname => '>', oprleft => 'jsonpath', oprright => 'jsonb',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_gt_jsonb(jsonpath,jsonb)' },
+{ oid => '6034', descr => 'jsonpath >= jsonb',
+ oprname => '>=', oprleft => 'jsonpath', oprright => 'jsonb',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_ge_jsonb(jsonpath,jsonb)' },
+{ oid => '6035', descr => 'jsonpath + jsonb',
+ oprname => '+', oprleft => 'jsonpath', oprright => 'jsonb',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_pl_jsonb(jsonpath,jsonb)' },
+{ oid => '6036', descr => 'jsonpath - jsonb',
+ oprname => '-', oprleft => 'jsonpath', oprright => 'jsonb',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_mi_jsonb(jsonpath,jsonb)' },
+{ oid => '6037', descr => 'jsonpath * jsonb',
+ oprname => '*', oprleft => 'jsonpath', oprright => 'jsonb',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_mul_jsonb(jsonpath,jsonb)' },
+{ oid => '6038', descr => 'jsonpath / jsonb',
+ oprname => '/', oprleft => 'jsonpath', oprright => 'jsonb',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_div_jsonb(jsonpath,jsonb)' },
+{ oid => '6039', descr => 'jsonpath % jsonb',
+ oprname => '%', oprleft => 'jsonpath', oprright => 'jsonb',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_mod_jsonb(jsonpath,jsonb)' },
+
+{ oid => '6040', descr => 'jsonpath -> text',
+ oprname => '->', oprleft => 'jsonpath', oprright => 'text',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_object_field(jsonpath,text)' },
+{ oid => '6041', descr => 'jsonpath -> int',
+ oprname => '->', oprleft => 'jsonpath', oprright => 'int4',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_array_element(jsonpath,int4)' },
+{ oid => '6042', descr => 'jsonpath ? jsonpath',
+ oprname => '?', oprleft => 'jsonpath', oprright => 'jsonpath',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_filter(jsonpath,jsonpath)' },
+{ oid => '6072', descr => 'jsonpath @ jsonb',
+ oprname => '@', oprleft => 'jsonpath', oprright => 'jsonb',
+ oprresult => 'jsonpath', oprcode => 'jsonpath_bind_jsonb(jsonpath,jsonb)' },
]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87335248a0..c0d98cdf26 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8174,6 +8174,10 @@
proname => 'json_agg_transfn', proisstrict => 'f', provolatile => 's',
prorettype => 'internal', proargtypes => 'internal anyelement',
prosrc => 'json_agg_transfn' },
+{ oid => '4035', 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' },
@@ -8181,10 +8185,18 @@
proname => 'json_agg', prokind => 'a', proisstrict => 'f', provolatile => 's',
prorettype => 'json', proargtypes => 'anyelement',
prosrc => 'aggregate_dummy' },
+{ oid => '3434', 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 => '3432', descr => 'json object aggregate transition function',
+ proname => 'json_objectagg_transfn', proisstrict => 'f', provolatile => 's',
+ prorettype => 'internal', proargtypes => 'internal any any bool bool',
+ prosrc => 'json_objectagg_transfn' },
{ oid => '3196', descr => 'json object aggregate final function',
proname => 'json_object_agg_finalfn', proisstrict => 'f',
prorettype => 'json', proargtypes => 'internal',
@@ -8193,6 +8205,10 @@
proname => 'json_object_agg', prokind => 'a', proisstrict => 'f',
provolatile => 's', prorettype => 'json', proargtypes => 'any any',
prosrc => 'aggregate_dummy' },
+{ oid => '3435', descr => 'aggregate input into a json object',
+ proname => 'json_objectagg', prokind => 'a', proisstrict => 'f',
+ provolatile => 's', prorettype => 'json', proargtypes => 'any any bool bool',
+ 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',
@@ -8202,6 +8218,11 @@
proname => 'json_build_array', proisstrict => 'f', provolatile => 's',
prorettype => 'json', proargtypes => '',
prosrc => 'json_build_array_noargs' },
+{ oid => '3998', descr => 'build a json array from any inputs',
+ proname => 'json_build_array_ext', provariadic => 'any', proisstrict => 'f',
+ provolatile => 's', prorettype => 'json', proargtypes => 'bool any',
+ proallargtypes => '{bool,any}', proargmodes => '{i,v}',
+ prosrc => 'json_build_array_ext' },
{ oid => '3200',
descr => 'build a json object from pairwise key/value inputs',
proname => 'json_build_object', provariadic => 'any', proisstrict => 'f',
@@ -8212,6 +8233,11 @@
proname => 'json_build_object', proisstrict => 'f', provolatile => 's',
prorettype => 'json', proargtypes => '',
prosrc => 'json_build_object_noargs' },
+{ oid => '6066', descr => 'build a json object from pairwise key/value inputs',
+ proname => 'json_build_object_ext', provariadic => 'any', proisstrict => 'f',
+ provolatile => 's', prorettype => 'json', proargtypes => 'bool bool any',
+ proallargtypes => '{bool,bool,any}', proargmodes => '{i,i,v}',
+ prosrc => 'json_build_object_ext' },
{ oid => '3202', descr => 'map text array of key value pairs to json object',
proname => 'json_object', prorettype => 'json', proargtypes => '_text',
prosrc => 'json_object' },
@@ -8224,6 +8250,13 @@
{ oid => '3261', descr => 'remove object fields with null values from json',
proname => 'json_strip_nulls', prorettype => 'json', proargtypes => 'json',
prosrc => 'json_strip_nulls' },
+{ oid => '6070', descr => 'check json value type and key uniqueness',
+ proname => 'json_is_valid', prorettype => 'bool',
+ proargtypes => 'json text bool', prosrc => 'json_is_valid' },
+{ oid => '6074',
+ descr => 'check json text validity, value type and key uniquenes',
+ proname => 'json_is_valid', prorettype => 'bool',
+ proargtypes => 'text text bool', prosrc => 'json_is_valid' },
{ oid => '3947',
proname => 'json_object_field', prorettype => 'json',
@@ -9027,6 +9060,10 @@
proname => 'jsonb_agg_transfn', proisstrict => 'f', provolatile => 's',
prorettype => 'internal', proargtypes => 'internal anyelement',
prosrc => 'jsonb_agg_transfn' },
+{ oid => '6065', 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',
@@ -9035,10 +9072,18 @@
proname => 'jsonb_agg', prokind => 'a', proisstrict => 'f',
provolatile => 's', prorettype => 'jsonb', proargtypes => 'anyelement',
prosrc => 'aggregate_dummy' },
+{ oid => '6063', 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 => '4142', descr => 'jsonb object aggregate transition function',
+ proname => 'jsonb_objectagg_transfn', proisstrict => 'f', provolatile => 's',
+ prorettype => 'internal', proargtypes => 'internal any any bool bool',
+ prosrc => 'jsonb_objectagg_transfn' },
{ oid => '3269', descr => 'jsonb object aggregate final function',
proname => 'jsonb_object_agg_finalfn', proisstrict => 'f', provolatile => 's',
prorettype => 'jsonb', proargtypes => 'internal',
@@ -9047,6 +9092,10 @@
proname => 'jsonb_object_agg', prokind => 'a', proisstrict => 'f',
prorettype => 'jsonb', proargtypes => 'any any',
prosrc => 'aggregate_dummy' },
+{ oid => '6064', descr => 'aggregate inputs into jsonb object',
+ proname => 'jsonb_objectagg', prokind => 'a', proisstrict => 'f',
+ prorettype => 'jsonb', proargtypes => 'any any bool bool',
+ 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',
@@ -9056,6 +9105,11 @@
proname => 'jsonb_build_array', proisstrict => 'f', provolatile => 's',
prorettype => 'jsonb', proargtypes => '',
prosrc => 'jsonb_build_array_noargs' },
+{ oid => '6068', descr => 'build a jsonb array from any inputs',
+ proname => 'jsonb_build_array_ext', provariadic => 'any', proisstrict => 'f',
+ provolatile => 's', prorettype => 'jsonb', proargtypes => 'bool any',
+ proallargtypes => '{bool,any}', proargmodes => '{i,v}',
+ prosrc => 'jsonb_build_array_ext' },
{ oid => '3273',
descr => 'build a jsonb object from pairwise key/value inputs',
proname => 'jsonb_build_object', provariadic => 'any', proisstrict => 'f',
@@ -9066,9 +9120,20 @@
proname => 'jsonb_build_object', proisstrict => 'f', provolatile => 's',
prorettype => 'jsonb', proargtypes => '',
prosrc => 'jsonb_build_object_noargs' },
+{ oid => '6067', descr => 'build a jsonb object from pairwise key/value inputs',
+ proname => 'jsonb_build_object_ext', provariadic => 'any', proisstrict => 'f',
+ provolatile => 's', prorettype => 'jsonb', proargtypes => 'bool bool any',
+ proallargtypes => '{bool,bool,any}', proargmodes => '{i,i,v}',
+ prosrc => 'jsonb_build_object_ext' },
{ oid => '3262', descr => 'remove object fields with null values from jsonb',
proname => 'jsonb_strip_nulls', prorettype => 'jsonb', proargtypes => 'jsonb',
prosrc => 'jsonb_strip_nulls' },
+{ oid => '6062', descr => 'check jsonb value type',
+ proname => 'jsonb_is_valid', prorettype => 'bool',
+ proargtypes => 'jsonb text', prosrc => 'jsonb_is_valid' },
+{ oid => '6116', descr => 'bytea to jsonb',
+ proname => 'jsonb_from_bytea', prorettype => 'jsonb', proargtypes => 'bytea',
+ prosrc => 'jsonb_from_bytea' },
{ oid => '3478',
proname => 'jsonb_object_field', prorettype => 'jsonb',
@@ -9275,6 +9340,10 @@
proname => 'jsonb_path_query_first', prorettype => 'jsonb',
proargtypes => 'jsonb jsonpath jsonb bool',
prosrc => 'jsonb_path_query_first' },
+{ oid => '6127', descr => 'jsonpath query first item text',
+ proname => 'jsonb_path_query_first_text', prorettype => 'text',
+ proargtypes => 'jsonb jsonpath jsonb bool',
+ prosrc => 'jsonb_path_query_first_text' },
{ oid => '4009', descr => 'jsonpath match',
proname => 'jsonb_path_match', prorettype => 'bool',
proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_match' },
@@ -9286,6 +9355,126 @@
proname => 'jsonb_path_match_opr', prorettype => 'bool',
proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_match_opr' },
+{ oid => '6043', descr => 'implementation of @? operator',
+ proname => 'json_path_exists', prorettype => 'bool',
+ proargtypes => 'json jsonpath', prosrc => 'json_path_exists_opr' },
+{ oid => '6047', descr => 'implementation of @@ operator',
+ proname => 'json_path_match', prorettype => 'bool',
+ proargtypes => 'json jsonpath', prosrc => 'json_path_match_opr' },
+
+{ oid => '6045', descr => 'jsonpath exists test',
+ proname => 'json_path_exists', prorettype => 'bool',
+ proargtypes => 'json jsonpath json bool', prosrc => 'json_path_exists' },
+{ oid => '6046', descr => 'jsonpath query',
+ proname => 'json_path_query', prorows => '1000', proretset => 't',
+ prorettype => 'json', proargtypes => 'json jsonpath json bool',
+ prosrc => 'json_path_query' },
+{ oid => '6129', descr => 'jsonpath query with conditional wrapper',
+ proname => 'json_path_query_array', prorettype => 'json',
+ proargtypes => 'json jsonpath json bool',
+ prosrc => 'json_path_query_array' },
+{ oid => '6069', descr => 'jsonpath match test',
+ proname => 'json_path_match', prorettype => 'bool',
+ proargtypes => 'json jsonpath json bool', prosrc => 'json_path_match' },
+{ oid => '6109', descr => 'jsonpath query first item',
+ proname => 'json_path_query_first', prorettype => 'json',
+ proargtypes => 'json jsonpath json bool',
+ prosrc => 'json_path_query_first' },
+{ oid => '6044', descr => 'jsonpath query first item text',
+ proname => 'json_path_query_first_text', prorettype => 'text',
+ proargtypes => 'json jsonpath json bool',
+ prosrc => 'json_path_query_first_text' },
+
+{ oid => '6077', descr => 'implementation of == operator',
+ proname => 'jsonpath_eq_jsonpath', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_eq_jsonpath' },
+{ oid => '6078', descr => 'implementation of != operator',
+ proname => 'jsonpath_ne_jsonpath', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_ne_jsonpath' },
+{ oid => '6079', descr => 'implementation of < operator',
+ proname => 'jsonpath_lt_jsonpath', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_lt_jsonpath' },
+{ oid => '6080', descr => 'implementation of <= operator',
+ proname => 'jsonpath_le_jsonpath', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_le_jsonpath' },
+{ oid => '6081', descr => 'implementation of > operator',
+ proname => 'jsonpath_gt_jsonpath', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_gt_jsonpath' },
+{ oid => '6082', descr => 'implementation of >= operator',
+ proname => 'jsonpath_ge_jsonpath', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_ge_jsonpath' },
+{ oid => '6083', descr => 'implementation of + operator',
+ proname => 'jsonpath_pl_jsonpath', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_pl_jsonpath' },
+{ oid => '6084', descr => 'implementation of - operator',
+ proname => 'jsonpath_mi_jsonpath', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_mi_jsonpath' },
+{ oid => '6085', descr => 'implementation of * operator',
+ proname => 'jsonpath_mul_jsonpath', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_mul_jsonpath' },
+{ oid => '6086', descr => 'implementation of / operator',
+ proname => 'jsonpath_div_jsonpath', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_div_jsonpath' },
+{ oid => '6087', descr => 'implementation of % operator',
+ proname => 'jsonpath_mod_jsonpath', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_mod_jsonpath' },
+
+{ oid => '6088', descr => 'implementation of == operator',
+ proname => 'jsonpath_eq_jsonb', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_eq_jsonb' },
+{ oid => '6089', descr => 'implementation of != operator',
+ proname => 'jsonpath_ne_jsonb', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_ne_jsonb' },
+{ oid => '6090', descr => 'implementation of < operator',
+ proname => 'jsonpath_lt_jsonb', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_lt_jsonb' },
+{ oid => '6091', descr => 'implementation of <= operator',
+ proname => 'jsonpath_le_jsonb', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_le_jsonb' },
+{ oid => '6092', descr => 'implementation of > operator',
+ proname => 'jsonpath_gt_jsonb', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_gt_jsonb' },
+{ oid => '6093', descr => 'implementation of >= operator',
+ proname => 'jsonpath_ge_jsonb', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_ge_jsonb' },
+{ oid => '6094', descr => 'implementation of + operator',
+ proname => 'jsonpath_pl_jsonb', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_pl_jsonb' },
+{ oid => '6095', descr => 'implementation of - operator',
+ proname => 'jsonpath_mi_jsonb', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_mi_jsonb' },
+{ oid => '6096', descr => 'implementation of * operator',
+ proname => 'jsonpath_mul_jsonb', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_mul_jsonb' },
+{ oid => '6097', descr => 'implementation of / operator',
+ proname => 'jsonpath_div_jsonb', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_div_jsonb' },
+{ oid => '6098', descr => 'implementation of % operator',
+ proname => 'jsonpath_mod_jsonb', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_mod_jsonb' },
+
+{ oid => '6099', descr => 'implementation of -> operator',
+ proname => 'jsonpath_object_field', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath text', prosrc => 'jsonpath_object_field' },
+{ oid => '6015', descr => 'implementation of -> operator',
+ proname => 'jsonpath_array_element', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath int4', prosrc => 'jsonpath_array_element' },
+{ oid => '6016', descr => 'implementation of ? operator',
+ proname => 'jsonpath_filter', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_filter' },
+{ oid => '6028', descr => 'implementation of @ operator',
+ proname => 'jsonpath_bind_jsonb', prorettype => 'jsonpath',
+ proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_bind_jsonb' },
+
+# jsonpath_fcxt
+{ oid => '6122', descr => 'I/O',
+ proname => 'jsonpath_fcxt_in', proisstrict => 'f',
+ prorettype => 'jsonpath_fcxt', proargtypes => 'cstring',
+ prosrc => 'jsonpath_fcxt_in' },
+{ oid => '6123', descr => 'I/O',
+ proname => 'jsonpath_fcxt_out', prorettype => 'cstring',
+ proargtypes => 'jsonpath_fcxt', prosrc => 'jsonpath_fcxt_out' },
+
# txid
{ oid => '2939', descr => 'I/O',
proname => 'txid_snapshot_in', prorettype => 'txid_snapshot',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index be49e00114..9dc14deb7f 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -594,5 +594,10 @@
typname => 'anyrange', typlen => '-1', typbyval => 'f', typtype => 'p',
typcategory => 'P', typinput => 'anyrange_in', typoutput => 'anyrange_out',
typreceive => '-', typsend => '-', typalign => 'd', typstorage => 'x' },
+{ oid => '6103',
+ typname => 'jsonpath_fcxt', typlen => 'SIZEOF_POINTER', typbyval => 't',
+ typtype => 'p', typcategory => 'P', typinput => 'jsonpath_fcxt_in',
+ typoutput => 'jsonpath_fcxt_out', typreceive => '-', typsend => '-',
+ typalign => 'ALIGNOF_POINTER' },
]
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 8e7f7c3d13..5eec3b1301 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -20,6 +20,7 @@
/* forward references to avoid circularity */
struct ExprEvalStep;
struct SubscriptingRefState;
+struct JsonItem;
/* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
/* expression's interpreter has been initialized */
@@ -219,6 +220,7 @@ typedef enum ExprEvalOp
EEOP_WINDOW_FUNC,
EEOP_SUBPLAN,
EEOP_ALTERNATIVE_SUBPLAN,
+ EEOP_JSONEXPR,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -654,6 +656,56 @@ typedef struct ExprEvalStep
int transno;
int setoff;
} agg_trans;
+
+ /* 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 */
+
+ struct
+ {
+ Datum value;
+ bool isnull;
+ } *raw_expr, /* raw context item value */
+ *res_expr, /* result item */
+ *coercion_expr, /* input for JSON item coercion */
+ *pathspec; /* path specification value */
+
+ ExprState *formatted_expr; /* formatted context item */
+ 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,
+ dbl,
+ boolean,
+ date,
+ time,
+ timetz,
+ timestamp,
+ timestamptz,
+ composite;
+ } coercions; /* states for coercion from SQL/JSON item
+ * types directly to the output type */
+ } jsonexpr;
+
} d;
} ExprEvalStep;
@@ -754,6 +806,18 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
+extern void ExecEvalJson(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern Datum ExecPrepareJsonItemCoercion(struct JsonItem *item, bool is_jsonb,
+ JsonReturning *returning,
+ struct JsonCoercionsState *coercions,
+ struct JsonCoercionState **pjcstate);
+extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
+ struct JsonCoercionsState *);
+extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
+ ExprContext *econtext, bool *isnull,
+ Datum caseval_datum,
+ bool caseval_isnull);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup);
extern Datum ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d056fd6151..cf24f55219 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -245,6 +245,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/funcapi.h b/src/include/funcapi.h
index ebba8b6f54..1b97159e4a 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -276,15 +276,20 @@ extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple);
*/
/* from funcapi.c */
-extern FuncCallContext *init_MultiFuncCall(PG_FUNCTION_ARGS);
-extern FuncCallContext *per_MultiFuncCall(PG_FUNCTION_ARGS);
-extern void end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx);
+extern FuncCallContext *init_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext **pfuncctx);
+extern FuncCallContext *per_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext **pfuncctx);
+extern void end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx, FuncCallContext **pfuncctx);
-#define SRF_IS_FIRSTCALL() (fcinfo->flinfo->fn_extra == NULL)
+#define SRF_DEFAULT_FCTX ((FuncCallContext **) &fcinfo->flinfo->fn_extra)
-#define SRF_FIRSTCALL_INIT() init_MultiFuncCall(fcinfo)
+#define SRF_IS_FIRSTCALL_EXT(_pfuncctx) (*(_pfuncctx) == NULL)
+#define SRF_IS_FIRSTCALL() SRF_IS_FIRSTCALL_EXT(SRF_DEFAULT_FCTX)
-#define SRF_PERCALL_SETUP() per_MultiFuncCall(fcinfo)
+#define SRF_FIRSTCALL_INIT_EXT(_pfuncctx) init_MultiFuncCall(fcinfo, _pfuncctx)
+#define SRF_FIRSTCALL_INIT() SRF_FIRSTCALL_INIT_EXT(SRF_DEFAULT_FCTX)
+
+#define SRF_PERCALL_SETUP_EXT(_pfuncctx) per_MultiFuncCall(fcinfo, _pfuncctx)
+#define SRF_PERCALL_SETUP() SRF_PERCALL_SETUP_EXT(SRF_DEFAULT_FCTX)
#define SRF_RETURN_NEXT(_funcctx, _result) \
do { \
@@ -304,15 +309,18 @@ extern void end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx);
PG_RETURN_NULL(); \
} while (0)
-#define SRF_RETURN_DONE(_funcctx) \
+#define SRF_RETURN_DONE_EXT(_funcctx, _pfuncctx) \
do { \
ReturnSetInfo *rsi; \
- end_MultiFuncCall(fcinfo, _funcctx); \
+ end_MultiFuncCall(fcinfo, _funcctx, _pfuncctx); \
rsi = (ReturnSetInfo *) fcinfo->resultinfo; \
rsi->isDone = ExprEndResult; \
PG_RETURN_NULL(); \
} while (0)
+#define SRF_RETURN_DONE(_funcctx) \
+ SRF_RETURN_DONE_EXT(_funcctx, SRF_DEFAULT_FCTX)
+
/*----------
* Support to ease writing of functions dealing with VARIADIC inputs
*----------
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index ad7b41d4aa..8d17083b41 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -100,4 +100,13 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat format);
+extern JsonBehavior *makeJsonBehavior(JsonBehaviorType type, Node *expr);
+extern Node *makeJsonTableJoinedPlan(JsonTablePlanJoinType type,
+ Node *plan1, Node *plan2, int location);
+extern Node *makeJsonKeyValue(Node *key, Node *value);
+extern Node *makeJsonIsPredicate(Node *expr, JsonFormat format,
+ JsonValueType vtype, bool unique_keys);
+extern JsonEncoding makeJsonEncoding(char *name);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 4e2fb39105..34e9405c61 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -196,6 +196,11 @@ typedef enum NodeTag
T_FromExpr,
T_OnConflictExpr,
T_IntoClause,
+ T_JsonExpr,
+ T_JsonCoercion,
+ T_JsonItemCoercions,
+ T_JsonTableParentNode,
+ T_JsonTableSiblingNode,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -476,6 +481,25 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
+ T_JsonValueExpr,
+ T_JsonObjectCtor,
+ T_JsonArrayCtor,
+ T_JsonArrayQueryCtor,
+ T_JsonObjectAgg,
+ T_JsonArrayAgg,
+ T_JsonFuncExpr,
+ T_JsonIsPredicate,
+ T_JsonExistsPredicate,
+ T_JsonTable,
+ T_JsonTableColumn,
+ T_JsonTablePlan,
+ T_JsonCommon,
+ T_JsonArgument,
+ T_JsonKeyValue,
+ T_JsonBehavior,
+ T_JsonOutput,
+ T_JsonCtorOpts,
+ T_JsonIsPredicateOpts,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 94ded3c135..d5d2544c95 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1463,6 +1463,310 @@ typedef struct TriggerTransition
bool isTable;
} TriggerTransition;
+/* Nodes for SQL/JSON support */
+
+/*
+ * JsonQuotes -
+ * representation of [KEEP|OMIT] QUOTES clause for JSON_QUERY()
+ */
+typedef enum JsonQuotes
+{
+ JS_QUOTES_UNSPEC, /* unspecified */
+ JS_QUOTES_KEEP, /* KEEP QUOTES */
+ JS_QUOTES_OMIT /* OMIT QUOTES */
+} JsonQuotes;
+
+/*
+ * JsonTableColumnType -
+ * enumeration of JSON_TABLE column types
+ */
+typedef enum
+{
+ JTC_FOR_ORDINALITY,
+ JTC_REGULAR,
+ JTC_FORMATTED,
+ JTC_NESTED,
+} JsonTableColumnType;
+
+/*
+ * JsonPathSpec -
+ * representation of JSON path constant
+ */
+typedef char *JsonPathSpec;
+
+/*
+ * JsonOutput -
+ * representation of JSON output clause (RETURNING type [FORMAT format])
+ */
+typedef struct JsonOutput
+{
+ NodeTag type;
+ TypeName *typeName; /* RETURNING type name, if specified */
+ JsonReturning returning; /* RETURNING FORMAT clause and type Oids */
+} JsonOutput;
+
+/*
+ * JsonValueExpr -
+ * representation of JSON value expression (expr [FORMAT json_format])
+ */
+typedef struct JsonValueExpr
+{
+ NodeTag type;
+ Expr *expr; /* raw expression */
+ JsonFormat format; /* FORMAT clause, if specified */
+} JsonValueExpr;
+
+/*
+ * 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;
+
+/*
+ * JsonTableColumn -
+ * untransformed representation of JSON_TABLE column
+ */
+typedef struct JsonTableColumn
+{
+ NodeTag type;
+ JsonTableColumnType coltype; /* column type */
+ char *name; /* column name */
+ TypeName *typeName; /* column type name */
+ JsonPathSpec pathspec; /* path specification, if any */
+ char *pathname; /* path name, if any */
+ JsonFormat format; /* JSON format clause, if specified */
+ JsonWrapper wrapper; /* WRAPPER behavior for formatted columns */
+ bool omit_quotes; /* omit or keep quotes on scalar strings? */
+ List *columns; /* nested columns */
+ JsonBehavior *on_empty; /* ON EMPTY behavior */
+ JsonBehavior *on_error; /* ON ERROR behavior */
+ int location; /* token location, or -1 if unknown */
+} JsonTableColumn;
+
+/*
+ * JsonTablePlanType -
+ * flags for JSON_TABLE plan node types representation
+ */
+typedef enum JsonTablePlanType
+{
+ JSTP_DEFAULT,
+ JSTP_SIMPLE,
+ JSTP_JOINED,
+} JsonTablePlanType;
+
+/*
+ * JsonTablePlanJoinType -
+ * flags for JSON_TABLE join types representation
+ */
+typedef enum JsonTablePlanJoinType
+{
+ JSTP_INNER = 0x01,
+ JSTP_OUTER = 0x02,
+ JSTP_CROSS = 0x04,
+ JSTP_UNION = 0x08,
+} JsonTablePlanJoinType;
+
+typedef struct JsonTablePlan JsonTablePlan;
+
+/*
+ * JsonTablePlan -
+ * untransformed representation of JSON_TABLE plan node
+ */
+struct JsonTablePlan
+{
+ NodeTag type;
+ JsonTablePlanType plan_type; /* plan type */
+ JsonTablePlanJoinType join_type; /* join type (for joined plan only) */
+ JsonTablePlan *plan1; /* first joined plan */
+ JsonTablePlan *plan2; /* second joined plan */
+ char *pathname; /* path name (for simple plan only) */
+ int location; /* token location, or -1 if unknown */
+};
+
+/*
+ * JsonTable -
+ * untransformed representation of JSON_TABLE
+ */
+typedef struct JsonTable
+{
+ NodeTag type;
+ JsonCommon *common; /* common JSON path syntax fields */
+ List *columns; /* list of JsonTableColumn */
+ JsonTablePlan *plan; /* join plan, if specified */
+ JsonBehavior *on_error; /* ON ERROR behavior, if specified */
+ Alias *alias; /* table alias in FROM clause */
+ bool lateral; /* does it have LATERAL prefix? */
+ int location; /* token location, or -1 if unknown */
+} JsonTable;
+
+/*
+ * 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 vtype; /* JSON item type */
+ bool unique_keys; /* check key uniqueness? */
+ int location; /* token location, or -1 if unknown */
+} JsonIsPredicate;
+
+typedef struct JsonIsPredicateOpts
+{
+ NodeTag type;
+ JsonValueType value_type; /* JSON item type */
+ bool unique_keys; /* check key uniqueness? */
+} JsonIsPredicateOpts;
+
+/*
+ * 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;
+
+/*
+ * JsonObjectCtor -
+ * untransformed representation of JSON_OBJECT() constructor
+ */
+typedef struct JsonObjectCtor
+{
+ 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 */
+} JsonObjectCtor;
+
+/*
+ * JsonArrayCtor -
+ * untransformed representation of JSON_ARRAY(element,...) constructor
+ */
+typedef struct JsonArrayCtor
+{
+ 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 */
+} JsonArrayCtor;
+
+/*
+ * JsonArrayQueryCtor -
+ * untransformed representation of JSON_ARRAY(subquery) constructor
+ */
+typedef struct JsonArrayQueryCtor
+{
+ 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 */
+} JsonArrayQueryCtor;
+
+/*
+ * JsonAggCtor -
+ * common fields of untransformed representation of
+ * JSON_ARRAYAGG() and JSON_OBJECTAGG()
+ */
+typedef struct JsonAggCtor
+{
+ 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 */
+} JsonAggCtor;
+
+/*
+ * JsonObjectAgg -
+ * untransformed representation of JSON_OBJECTAGG()
+ */
+typedef struct JsonObjectAgg
+{
+ JsonAggCtor ctor; /* 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
+{
+ JsonAggCtor ctor; /* common fields */
+ JsonValueExpr *arg; /* array element expression */
+ bool absent_on_null; /* skip NULL elements? */
+} JsonArrayAgg;
+
+typedef struct JsonCtorOpts
+{
+ NodeTag type;
+ JsonReturning returning; /* RETURNING clause */
+ bool absent_on_null; /* skip NULL values? */
+ bool unique; /* check key uniqueness? */
+} JsonCtorOpts;
+
/*****************************************************************************
* Raw Grammar Output Statements
*****************************************************************************/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7c278c0e56..ecb3e833b7 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -73,6 +73,12 @@ typedef struct RangeVar
int location; /* token location, or -1 if unknown */
} RangeVar;
+typedef enum TableFuncType
+{
+ TFT_XMLTABLE,
+ TFT_JSON_TABLE
+} TableFuncType;
+
/*
* TableFunc - node for a table function, such as XMLTABLE.
*
@@ -82,6 +88,7 @@ typedef struct RangeVar
typedef struct TableFunc
{
NodeTag type;
+ TableFuncType functype; /* XMLTABLE or JSON_TABLE */
List *ns_uris; /* list of namespace URI expressions */
List *ns_names; /* list of namespace names or NULL */
Node *docexpr; /* input document expression */
@@ -92,7 +99,9 @@ typedef struct TableFunc
List *colcollations; /* OID list of column collation OIDs */
List *colexprs; /* list of column filter expressions */
List *coldefexprs; /* list of column default expressions */
+ List *colvalexprs; /* list of column value expressions */
Bitmapset *notnulls; /* nullability flag for each output column */
+ Node *plan; /* JSON_TABLE plan */
int ordinalitycol; /* counts from 0; -1 if none specified */
int location; /* token location, or -1 if unknown */
} TableFunc;
@@ -253,6 +262,16 @@ typedef struct Param
int location; /* token location, or -1 if unknown */
} Param;
+typedef enum FuncFormat
+{
+ FUNCFMT_REGULAR = 0,
+ FUNCFMT_JSON_OBJECT = 1,
+ FUNCFMT_JSON_ARRAY = 2,
+ FUNCFMT_JSON_OBJECTAGG = 3,
+ FUNCFMT_JSON_ARRAYAGG = 4,
+ FUNCFMT_IS_JSON = 5
+} FuncFormat;
+
/*
* Aggref
*
@@ -312,6 +331,8 @@ typedef struct Aggref
char aggkind; /* aggregate kind (see pg_aggregate.h) */
Index agglevelsup; /* > 0 if agg belongs to outer query */
AggSplit aggsplit; /* expected agg-splitting mode of parent Agg */
+ FuncFormat aggformat; /* how to display this aggregate */
+ Node *aggformatopts; /* display options, if any */
int location; /* token location, or -1 if unknown */
} Aggref;
@@ -365,6 +386,8 @@ typedef struct WindowFunc
Index winref; /* index of associated WindowClause */
bool winstar; /* true if argument list was really '*' */
bool winagg; /* is function a simple aggregate? */
+ FuncFormat winformat; /* how to display this window function */
+ Node *winformatopts; /* display options, if any */
int location; /* token location, or -1 if unknown */
} WindowFunc;
@@ -443,7 +466,8 @@ typedef enum CoercionForm
{
COERCE_EXPLICIT_CALL, /* display as a function call */
COERCE_EXPLICIT_CAST, /* display as an explicit cast */
- COERCE_IMPLICIT_CAST /* implicit cast, so hide it */
+ COERCE_IMPLICIT_CAST, /* implicit cast, so hide it */
+ COERCE_INTERNAL_CAST /* internal cast, so hide it always */
} CoercionForm;
/*
@@ -461,6 +485,8 @@ typedef struct FuncExpr
Oid funccollid; /* OID of collation of result */
Oid inputcollid; /* OID of collation that function should use */
List *args; /* arguments to the function */
+ FuncFormat funcformat2; /* how to display this function call */
+ Node *funcformatopts; /* display options, if any */
int location; /* token location, or -1 if unknown */
} FuncExpr;
@@ -1175,6 +1201,198 @@ 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() */
+ IS_JSON_TABLE /* JSON_TABLE() */
+} JsonExprOp;
+
+/*
+ * JsonEncoding -
+ * representation of JSON ENCODING clause
+ */
+typedef enum JsonEncoding
+{
+ JS_ENC_DEFAULT, /* unspecified */
+ JS_ENC_UTF8,
+ JS_ENC_UTF16,
+ JS_ENC_UTF32,
+} JsonEncoding;
+
+/*
+ * JsonFormatType -
+ * enumeration of JSON formats used in JSON FORMAT clause
+ */
+typedef enum JsonFormatType
+{
+ JS_FORMAT_DEFAULT, /* unspecified */
+ JS_FORMAT_JSON, /* FORMAT JSON [ENCODING ...] */
+ JS_FORMAT_JSONB /* implicit internal format for RETURNING jsonb */
+} JsonFormatType;
+
+/*
+ * JsonBehaviorType -
+ * enumeration of behavior types used in JSON ON ... BEHAVIOR clause
+ */
+typedef enum
+{
+ JSON_BEHAVIOR_NULL,
+ JSON_BEHAVIOR_ERROR,
+ JSON_BEHAVIOR_EMPTY,
+ JSON_BEHAVIOR_TRUE,
+ JSON_BEHAVIOR_FALSE,
+ JSON_BEHAVIOR_UNKNOWN,
+ JSON_BEHAVIOR_EMPTY_ARRAY,
+ JSON_BEHAVIOR_EMPTY_OBJECT,
+ JSON_BEHAVIOR_DEFAULT,
+} JsonBehaviorType;
+
+/*
+ * JsonWrapper -
+ * representation of WRAPPER clause for JSON_QUERY()
+ */
+typedef enum JsonWrapper
+{
+ JSW_NONE,
+ JSW_CONDITIONAL,
+ JSW_UNCONDITIONAL,
+} JsonWrapper;
+
+/*
+ * JsonFormat -
+ * representation of JSON FORMAT clause
+ */
+typedef struct JsonFormat
+{
+ JsonFormatType 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
+{
+ JsonFormat format; /* output JSON format */
+ Oid typid; /* target type Oid */
+ int32 typmod; /* target type modifier */
+} JsonReturning;
+
+/*
+ * JsonBehavior -
+ * representation of JSON ON ... BEHAVIOR clause
+ */
+typedef struct JsonBehavior
+{
+ NodeTag type;
+ JsonBehaviorType btype; /* behavior type */
+ Node *default_expr; /* default expression, if any */
+} JsonBehavior;
+
+/*
+ * JsonPassing -
+ * representation of JSON PASSING clause
+ */
+typedef struct JsonPassing
+{
+ List *values; /* list of PASSING argument expressions */
+ List *names; /* parallel list of Value strings */
+} JsonPassing;
+
+/*
+ * 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 *dbl;
+ 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 *raw_expr; /* raw context item expression */
+ 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 */
+ JsonPassing passing; /* PASSING clause arguments */
+ 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;
+
+/*
+ * JsonTableParentNode -
+ * transformed representation of parent JSON_TABLE plan node
+ */
+typedef struct JsonTableParentNode
+{
+ NodeTag type;
+ Const *path; /* jsonpath constant */
+ char *name; /* path name */
+ JsonPassing passing; /* PASSING arguments */
+ Node *child; /* nested columns, if any */
+ bool outerJoin; /* outer or inner join for nested columns? */
+ int colMin; /* min column index in the resulting column list */
+ int colMax; /* max column index in the resulting column list */
+ bool errorOnError; /* ERROR/EMPTY ON ERROR behavior */
+} JsonTableParentNode;
+
+/*
+ * JsonTableSiblingNode -
+ * transformed representation of joined sibling JSON_TABLE plan node
+ */
+typedef struct JsonTableSiblingNode
+{
+ NodeTag type;
+ Node *larg; /* left join node */
+ Node *rarg; /* right join node */
+ bool cross; /* cross or union join? */
+} JsonTableSiblingNode;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace8425e..5383c3c0fc 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -26,6 +26,7 @@
/* name, value, category */
PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("absent", ABSENT, UNRESERVED_KEYWORD)
PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD)
PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD)
@@ -88,6 +89,7 @@ PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD)
PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("conditional", CONDITIONAL, UNRESERVED_KEYWORD)
PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD)
PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD)
PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD)
@@ -141,11 +143,13 @@ PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD)
PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD)
PG_KEYWORD("else", ELSE, RESERVED_KEYWORD)
+PG_KEYWORD("empty", EMPTY_P, UNRESERVED_KEYWORD)
PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD)
PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD)
PG_KEYWORD("end", END_P, RESERVED_KEYWORD)
PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("error", ERROR_P, UNRESERVED_KEYWORD)
PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
PG_KEYWORD("event", EVENT, UNRESERVED_KEYWORD)
PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
@@ -168,6 +172,7 @@ PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
PG_KEYWORD("for", FOR, RESERVED_KEYWORD)
PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD)
PG_KEYWORD("foreign", FOREIGN, RESERVED_KEYWORD)
+PG_KEYWORD("format", FORMAT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("forward", FORWARD, UNRESERVED_KEYWORD)
PG_KEYWORD("freeze", FREEZE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("from", FROM, RESERVED_KEYWORD)
@@ -220,7 +225,19 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD)
PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD)
+PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD)
+PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD)
+PG_KEYWORD("json_exists", JSON_EXISTS, COL_NAME_KEYWORD)
+PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD)
+PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD)
+PG_KEYWORD("json_query", JSON_QUERY, COL_NAME_KEYWORD)
+PG_KEYWORD("json_table", JSON_TABLE, COL_NAME_KEYWORD)
+PG_KEYWORD("json_value", JSON_VALUE, COL_NAME_KEYWORD)
+PG_KEYWORD("jsonb", JSONB, UNRESERVED_KEYWORD)
+PG_KEYWORD("keep", KEEP, UNRESERVED_KEYWORD)
PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD)
+PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD)
PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD)
PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD)
PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD)
@@ -257,6 +274,7 @@ PG_KEYWORD("names", NAMES, UNRESERVED_KEYWORD)
PG_KEYWORD("national", NATIONAL, COL_NAME_KEYWORD)
PG_KEYWORD("natural", NATURAL, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("nchar", NCHAR, COL_NAME_KEYWORD)
+PG_KEYWORD("nested", NESTED, UNRESERVED_KEYWORD)
PG_KEYWORD("new", NEW, UNRESERVED_KEYWORD)
PG_KEYWORD("next", NEXT, UNRESERVED_KEYWORD)
PG_KEYWORD("no", NO, UNRESERVED_KEYWORD)
@@ -276,6 +294,7 @@ PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD)
PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD)
PG_KEYWORD("oids", OIDS, UNRESERVED_KEYWORD)
PG_KEYWORD("old", OLD, UNRESERVED_KEYWORD)
+PG_KEYWORD("omit", OMIT, UNRESERVED_KEYWORD)
PG_KEYWORD("on", ON, RESERVED_KEYWORD)
PG_KEYWORD("only", ONLY, RESERVED_KEYWORD)
PG_KEYWORD("operator", OPERATOR, UNRESERVED_KEYWORD)
@@ -299,7 +318,9 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD)
PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
+PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD)
PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
@@ -317,6 +338,7 @@ PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD)
PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD)
PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD)
PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
+PG_KEYWORD("quotes", QUOTES, UNRESERVED_KEYWORD)
PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
PG_KEYWORD("real", REAL, COL_NAME_KEYWORD)
@@ -350,6 +372,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD)
+PG_KEYWORD("scalar", SCALAR, UNRESERVED_KEYWORD)
PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD)
PG_KEYWORD("schemas", SCHEMAS, UNRESERVED_KEYWORD)
PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD)
@@ -385,6 +408,7 @@ PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD)
PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD)
PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD)
PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("string", STRING, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD)
PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD)
PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD)
@@ -418,6 +442,7 @@ PG_KEYWORD("type", TYPE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("types", TYPES_P, UNRESERVED_KEYWORD)
PG_KEYWORD("unbounded", UNBOUNDED, UNRESERVED_KEYWORD)
PG_KEYWORD("uncommitted", UNCOMMITTED, UNRESERVED_KEYWORD)
+PG_KEYWORD("unconditional", UNCONDITIONAL, UNRESERVED_KEYWORD)
PG_KEYWORD("unencrypted", UNENCRYPTED, UNRESERVED_KEYWORD)
PG_KEYWORD("union", UNION, RESERVED_KEYWORD)
PG_KEYWORD("unique", UNIQUE, RESERVED_KEYWORD)
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bec129aff1..06ebb7dc02 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -70,11 +70,17 @@ typedef struct
/* date.c */
extern int32 anytime_typmod_check(bool istz, int32 typmod);
extern double date2timestamp_no_overflow(DateADT dateVal);
+extern Timestamp date2timestamp_internal(DateADT dateVal, bool *error);
+extern TimestampTz date2timestamptz_internal(DateADT dateVal, int *tzp,
+ bool *error);
extern void EncodeSpecialDate(DateADT dt, char *str);
extern DateADT GetSQLCurrentDate(void);
extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
extern TimeADT GetSQLLocalTime(int32 typmod);
extern int time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
extern int timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
+extern int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
+extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
#endif /* DATE_H */
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 4de78ebe36..3f2d32af94 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -338,4 +338,8 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
int n);
extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
+extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
+ bool *error);
+
#endif /* DATETIME_H */
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index 543d00e591..cb311e2119 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -148,18 +148,41 @@ check_float4_val(const float4 val, const bool inf_is_valid,
}
static inline void
-check_float8_val(const float8 val, const bool inf_is_valid,
- const bool zero_is_valid)
+check_float8_val_error(const float8 val, const bool inf_is_valid,
+ const bool zero_is_valid, bool *error)
{
if (!inf_is_valid && unlikely(isinf(val)))
+ {
+ if (error)
+ {
+ *error = true;
+ return;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: overflow")));
+ }
if (!zero_is_valid && unlikely(val == 0.0))
+ {
+ if (error)
+ {
+ *error = true;
+ return;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: underflow")));
+ }
+}
+
+static inline void
+check_float8_val(const float8 val, const bool inf_is_valid,
+ const bool zero_is_valid)
+{
+ check_float8_val_error(val, inf_is_valid, zero_is_valid, NULL);
}
/*
@@ -184,16 +207,22 @@ float4_pl(const float4 val1, const float4 val2)
}
static inline float8
-float8_pl(const float8 val1, const float8 val2)
+float8_pl_error(const float8 val1, const float8 val2, bool *error)
{
float8 result;
result = val1 + val2;
- check_float8_val(result, isinf(val1) || isinf(val2), true);
+ check_float8_val_error(result, isinf(val1) || isinf(val2), true, error);
return result;
}
+static inline float8
+float8_pl(const float8 val1, const float8 val2)
+{
+ return float8_pl_error(val1, val2, NULL);
+}
+
static inline float4
float4_mi(const float4 val1, const float4 val2)
{
@@ -206,16 +235,22 @@ float4_mi(const float4 val1, const float4 val2)
}
static inline float8
-float8_mi(const float8 val1, const float8 val2)
+float8_mi_error(const float8 val1, const float8 val2, bool *error)
{
float8 result;
result = val1 - val2;
- check_float8_val(result, isinf(val1) || isinf(val2), true);
+ check_float8_val_error(result, isinf(val1) || isinf(val2), true, error);
return result;
}
+static inline float8
+float8_mi(const float8 val1, const float8 val2)
+{
+ return float8_mi_error(val1, val2, NULL);
+}
+
static inline float4
float4_mul(const float4 val1, const float4 val2)
{
@@ -229,17 +264,23 @@ float4_mul(const float4 val1, const float4 val2)
}
static inline float8
-float8_mul(const float8 val1, const float8 val2)
+float8_mul_error(const float8 val1, const float8 val2, bool *error)
{
float8 result;
result = val1 * val2;
- check_float8_val(result, isinf(val1) || isinf(val2),
- val1 == 0.0 || val2 == 0.0);
+ check_float8_val_error(result, isinf(val1) || isinf(val2),
+ val1 == 0.0 || val2 == 0.0, error);
return result;
}
+static inline float8
+float8_mul(const float8 val1, const float8 val2)
+{
+ return float8_mul_error(val1, val2, NULL);
+}
+
static inline float4
float4_div(const float4 val1, const float4 val2)
{
@@ -257,21 +298,36 @@ float4_div(const float4 val1, const float4 val2)
}
static inline float8
-float8_div(const float8 val1, const float8 val2)
+float8_div_error(const float8 val1, const float8 val2, bool *error)
{
float8 result;
if (val2 == 0.0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0.0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ }
result = val1 / val2;
- check_float8_val(result, isinf(val1) || isinf(val2), val1 == 0.0);
+ check_float8_val_error(result, isinf(val1) || isinf(val2), val1 == 0.0,
+ error);
return result;
}
+static inline float8
+float8_div(const float8 val1, const float8 val2)
+{
+ return float8_div_error(val1, val2, NULL);
+}
+
/*
* Routines for NaN-aware comparisons
*
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index 5b275dc985..c9efdd1f03 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
extern char *asc_toupper(const char *buff, size_t nbytes);
extern char *asc_initcap(const char *buff, size_t nbytes);
+extern Datum parse_datetime(text *date_txt, text *fmt_txt, char *tzname,
+ bool strict, Oid *typid, int32 *typmod, int *tz, bool *error);
+
#endif
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 5f4d479a7b..ab33f7bb50 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -15,6 +15,7 @@
#define JSONAPI_H
#include "jsonb.h"
+#include "access/htup.h"
#include "lib/stringinfo.h"
typedef enum
@@ -60,6 +61,8 @@ typedef struct JsonLexContext
int line_number;
char *line_start;
StringInfo strval;
+ bool throw_errors;
+ bool error;
} JsonLexContext;
typedef void (*json_struct_action) (void *state);
@@ -93,6 +96,56 @@ typedef struct JsonSemAction
json_scalar_action scalar;
} JsonSemAction;
+typedef enum
+{
+ JTI_ARRAY_START,
+ JTI_ARRAY_ELEM,
+ JTI_ARRAY_ELEM_SCALAR,
+ JTI_ARRAY_ELEM_AFTER,
+ JTI_ARRAY_END,
+ JTI_OBJECT_START,
+ JTI_OBJECT_KEY,
+ JTI_OBJECT_VALUE,
+ JTI_OBJECT_VALUE_AFTER,
+} JsontIterState;
+
+typedef struct JsonContainerData
+{
+ uint32 header;
+ int len;
+ char *data;
+} JsonContainerData;
+
+typedef const JsonContainerData JsonContainer;
+
+#define JsonTextContainerSize(jc) \
+ (((jc)->header & JB_CMASK) == JB_CMASK && JsonContainerIsArray(jc) \
+ ? JsonGetArraySize(jc) : (jc)->header & JB_CMASK)
+
+typedef struct Json
+{
+ JsonContainer root;
+} Json;
+
+typedef struct JsonIterator
+{
+ struct JsonIterator *parent;
+ JsonContainer *container;
+ JsonLexContext *lex;
+ JsontIterState state;
+ bool isScalar;
+} JsonIterator;
+
+#define DatumGetJsonP(datum) JsonCreate(DatumGetTextP(datum))
+#define DatumGetJsonPCopy(datum) JsonCreate(DatumGetTextPCopy(datum))
+
+#define JsonPGetDatum(json) \
+ PointerGetDatum(cstring_to_text_with_len((json)->root.data, (json)->root.len))
+
+#define PG_GETARG_JSON_P(n) DatumGetJsonP(PG_GETARG_DATUM(n))
+#define PG_GETARG_JSON_P_COPY(n) DatumGetJsonPCopy(PG_GETARG_DATUM(n))
+#define PG_RETURN_JSON_P(json) PG_RETURN_DATUM(JsonPGetDatum(json))
+
/*
* parse_json will parse the string in the lex calling the
* action functions in sem at the appropriate points. It is
@@ -102,7 +155,7 @@ typedef struct JsonSemAction
* points to. If the action pointers are NULL the parser
* does nothing and just continues.
*/
-extern void pg_parse_json(JsonLexContext *lex, JsonSemAction *sem);
+extern bool pg_parse_json(JsonLexContext *lex, JsonSemAction *sem);
/*
* json_count_array_elements performs a fast secondary parse to determine the
@@ -161,6 +214,31 @@ 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 char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+ const int *tzp);
+
+extern Datum json_populate_type(Datum json_val, Oid json_type,
+ Oid typid, int32 typmod,
+ void **cache, MemoryContext mcxt, bool *isnull);
+
+extern Json *JsonCreate(text *json);
+extern JsonbIteratorToken JsonIteratorNext(JsonIterator **pit, JsonbValue *val,
+ bool skipNested);
+extern JsonIterator *JsonIteratorInit(JsonContainer *jc);
+extern void JsonIteratorFree(JsonIterator *it);
+extern uint32 JsonGetArraySize(JsonContainer *jc);
+extern Json *JsonbValueToJson(JsonbValue *jbv);
+extern bool JsonExtractScalar(JsonContainer *jbc, JsonbValue *res);
+extern char *JsonUnquote(Json *jb);
+extern char *JsonToCString(StringInfo out, JsonContainer *jc,
+ int estimated_len);
+extern JsonbValue *pushJsonValue(JsonbParseState **pstate,
+ JsonbIteratorToken tok, JsonbValue *jbv);
+extern JsonbValue *jsonFindLastKeyInObject(JsonContainer *obj,
+ const char *keyval, int keylen, JsonbValue *res);
+extern JsonbValue *findJsonValueFromContainer(JsonContainer *jc, uint32 flags,
+ JsonbValue *key, JsonbValue *res);
+extern JsonbValue *getIthJsonValueFromContainer(JsonContainer *array,
+ uint32 index, JsonbValue *res);
#endif /* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 2fe7d32fec..8d56d664c8 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -224,10 +224,10 @@ typedef struct
} Jsonb;
/* convenience macros for accessing the root container in a Jsonb datum */
-#define JB_ROOT_COUNT(jbp_) (*(uint32 *) VARDATA(jbp_) & JB_CMASK)
-#define JB_ROOT_IS_SCALAR(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FSCALAR) != 0)
-#define JB_ROOT_IS_OBJECT(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FOBJECT) != 0)
-#define JB_ROOT_IS_ARRAY(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FARRAY) != 0)
+#define JB_ROOT_COUNT(jbp_) JsonContainerSize(&(jbp_)->root)
+#define JB_ROOT_IS_SCALAR(jbp_) JsonContainerIsScalar(&(jbp_)->root)
+#define JB_ROOT_IS_OBJECT(jbp_) JsonContainerIsObject(&(jbp_)->root)
+#define JB_ROOT_IS_ARRAY(jbp_) JsonContainerIsArray(&(jbp_)->root)
enum jbvType
@@ -241,7 +241,7 @@ enum jbvType
jbvArray = 0x10,
jbvObject,
/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
- jbvBinary
+ jbvBinary,
};
/*
@@ -275,6 +275,8 @@ struct JsonbValue
{
int nPairs; /* 1 pair, 2 elements */
JsonbPair *pairs;
+ bool uniquify; /* Should we sort pairs by key name and
+ * remove duplicate keys? */
} object; /* Associative container type */
struct
@@ -285,8 +287,8 @@ struct JsonbValue
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool))
/*
* Key/value pair within an Object.
@@ -360,14 +362,23 @@ typedef struct JsonbIterator
/* Support functions */
extern uint32 getJsonbOffset(const JsonbContainer *jc, int index);
extern uint32 getJsonbLength(const JsonbContainer *jc, int index);
+extern int lengthCompareJsonbStringValue(const void *a, const void *b);
+extern bool equalsJsonbScalarValue(JsonbValue *a, JsonbValue *b);
extern int compareJsonbContainers(JsonbContainer *a, JsonbContainer *b);
extern JsonbValue *findJsonbValueFromContainer(JsonbContainer *sheader,
- uint32 flags,
- JsonbValue *key);
+ uint32 flags, JsonbValue *key,
+ JsonbValue *res);
+extern JsonbValue *jsonbFindKeyInObject(JsonbContainer *container,
+ const char *keyVal, int keyLen,
+ JsonbValue *res);
extern JsonbValue *getIthJsonbValueFromContainer(JsonbContainer *sheader,
- uint32 i);
+ uint32 i, JsonbValue *result);
+
extern JsonbValue *pushJsonbValue(JsonbParseState **pstate,
JsonbIteratorToken seq, JsonbValue *jbVal);
+extern JsonbValue *pushJsonbValueScalar(JsonbParseState **pstate,
+ JsonbIteratorToken seq,
+ JsonbValue *scalarVal);
extern JsonbIterator *JsonbIteratorInit(JsonbContainer *container);
extern JsonbIteratorToken JsonbIteratorNext(JsonbIterator **it, JsonbValue *val,
bool skipNested);
@@ -377,12 +388,16 @@ extern bool JsonbDeepContains(JsonbIterator **val,
extern void JsonbHashScalarValue(const JsonbValue *scalarVal, uint32 *hash);
extern void JsonbHashScalarValueExtended(const JsonbValue *scalarVal,
uint64 *hash, uint64 seed);
+extern bool JsonbValidate(const void *data, uint32 size);
/* jsonb.c support functions */
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/jsonpath.h b/src/include/utils/jsonpath.h
index 3e9d60cb76..5b3cdbc091 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -15,13 +15,18 @@
#define JSONPATH_H
#include "fmgr.h"
+#include "executor/tablefunc.h"
#include "utils/jsonb.h"
+#include "utils/jsonapi.h"
#include "nodes/pg_list.h"
+#include "nodes/primnodes.h"
+#include "utils/jsonb.h"
typedef struct
{
int32 vl_len_; /* varlena header (do not touch directly!) */
uint32 header; /* version and flags (see below) */
+ uint32 ext_items_count; /* number of items that need cache for external execution */
char data[FLEXIBLE_ARRAY_MEMBER];
} JsonPath;
@@ -29,6 +34,9 @@ typedef struct
#define JSONPATH_LAX (0x80000000)
#define JSONPATH_HDRSZ (offsetof(JsonPath, data))
+/* flags for JsonPathItem */
+#define JSPI_OUT_PATH 0x01
+
#define DatumGetJsonPathP(d) ((JsonPath *) DatumGetPointer(PG_DETOAST_DATUM(d)))
#define DatumGetJsonPathPCopy(d) ((JsonPath *) DatumGetPointer(PG_DETOAST_DATUM_COPY(d)))
#define PG_GETARG_JSONPATH_P(x) DatumGetJsonPathP(PG_GETARG_DATUM(x))
@@ -69,6 +77,7 @@ typedef enum JsonPathItemType
jpiAny, /* .** */
jpiKey, /* .key */
jpiCurrent, /* @ */
+ jpiCurrentN, /* @N */
jpiRoot, /* $ */
jpiVariable, /* $variable */
jpiFilter, /* ? (predicate) */
@@ -79,11 +88,22 @@ typedef enum JsonPathItemType
jpiFloor, /* .floor() item method */
jpiCeiling, /* .ceiling() item method */
jpiDouble, /* .double() item method */
+ jpiDatetime, /* .datetime() item method */
jpiKeyValue, /* .keyvalue() item method */
jpiSubscript, /* array subscript: 'expr' or 'expr TO expr' */
jpiLast, /* LAST array subscript */
jpiStartsWith, /* STARTS WITH predicate */
jpiLikeRegex, /* LIKE_REGEX predicate */
+ jpiSequence, /* sequence constructor: 'expr, ...' */
+ jpiArray, /* array constructor: '[expr, ...]' */
+ jpiObject, /* object constructor: '{ key : value, ... }' */
+ jpiObjectField, /* element of object constructor: 'key : value' */
+ jpiLambda, /* lambda expression: 'arg => expr' or '(arg,...) => expr' */
+ jpiArgument, /* lambda argument */
+ jpiMethod, /* user item method */
+ jpiFunction, /* user function */
+
+ jpiBinary = 0xFF /* for jsonpath operators implementation only */
} JsonPathItemType;
/* XQuery regex mode flags for LIKE_REGEX predicate */
@@ -91,6 +111,22 @@ typedef enum JsonPathItemType
#define JSP_REGEX_SLINE 0x02 /* s flag, single-line mode */
#define JSP_REGEX_MLINE 0x04 /* m flag, multi-line mode */
#define JSP_REGEX_WSPACE 0x08 /* x flag, expanded syntax */
+#define JSP_REGEX_QUOTE 0x10 /* q flag, no special characters */
+
+#define jspIsBooleanOp(type) ( \
+ (type) == jpiAnd || \
+ (type) == jpiOr || \
+ (type) == jpiNot || \
+ (type) == jpiIsUnknown || \
+ (type) == jpiEqual || \
+ (type) == jpiNotEqual || \
+ (type) == jpiLess || \
+ (type) == jpiGreater || \
+ (type) == jpiLessOrEqual || \
+ (type) == jpiGreaterOrEqual || \
+ (type) == jpiExists || \
+ (type) == jpiStartsWith \
+)
/*
* Support functions to parse/construct binary value.
@@ -103,6 +139,7 @@ typedef enum JsonPathItemType
typedef struct JsonPathItem
{
JsonPathItemType type;
+ uint8 flags;
/* position form base to next node */
int32 nextPos;
@@ -143,6 +180,27 @@ typedef struct JsonPathItem
uint32 last;
} anybounds;
+ struct
+ {
+ int32 nelems;
+ int32 *elems;
+ } sequence;
+
+ struct
+ {
+ int32 nfields;
+ struct
+ {
+ int32 key;
+ int32 val;
+ } *fields;
+ } object;
+
+ struct
+ {
+ int32 level;
+ } current;
+
struct
{
char *data; /* for bool, numeric and string/key */
@@ -156,10 +214,29 @@ typedef struct JsonPathItem
int32 patternlen;
uint32 flags;
} like_regex;
+
+ struct
+ {
+ int32 id;
+ int32 *params;
+ int32 nparams;
+ int32 expr;
+ } lambda;
+
+ struct
+ {
+ int32 id;
+ int32 item;
+ char *name;
+ int32 namelen;
+ int32 *args;
+ int32 nargs;
+ } func;
} content;
} JsonPathItem;
#define jspHasNext(jsp) ((jsp)->nextPos > 0)
+#define jspOutPath(jsp) (((jsp)->flags & JSPI_OUT_PATH) != 0)
extern void jspInit(JsonPathItem *v, JsonPath *js);
extern void jspInitByBuffer(JsonPathItem *v, char *base, int32 pos);
@@ -172,6 +249,15 @@ extern bool jspGetBool(JsonPathItem *v);
extern char *jspGetString(JsonPathItem *v, int32 *len);
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
JsonPathItem *to, int i);
+extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem);
+extern void jspGetObjectField(JsonPathItem *v, int i,
+ JsonPathItem *key, JsonPathItem *val);
+extern JsonPathItem *jspGetLambdaParam(JsonPathItem *func, int index,
+ JsonPathItem *arg);
+extern JsonPathItem *jspGetLambdaExpr(JsonPathItem *lambda, JsonPathItem *expr);
+extern JsonPathItem *jspGetFunctionArg(JsonPathItem *func, int index,
+ JsonPathItem *arg);
+extern JsonPathItem *jspGetMethodItem(JsonPathItem *method, JsonPathItem *arg);
extern const char *jspOperationName(JsonPathItemType type);
@@ -184,6 +270,7 @@ typedef struct JsonPathParseItem JsonPathParseItem;
struct JsonPathParseItem
{
JsonPathItemType type;
+ uint8 flags;
JsonPathParseItem *next; /* next in path */
union
@@ -203,7 +290,7 @@ struct JsonPathParseItem
struct
{
int nelems;
- struct
+ struct JsonPathParseArraySubscript
{
JsonPathParseItem *from;
JsonPathParseItem *to;
@@ -225,6 +312,31 @@ struct JsonPathParseItem
uint32 flags;
} like_regex;
+ struct {
+ List *elems;
+ } sequence;
+
+ struct {
+ List *fields;
+ } object;
+
+ struct {
+ int level;
+ } current;
+
+ JsonPath *binary;
+
+ struct {
+ List *params;
+ JsonPathParseItem *expr;
+ } lambda;
+
+ struct {
+ List *args;
+ char *name;
+ int32 namelen;
+ } func;
+
/* scalars */
Numeric numeric;
bool boolean;
@@ -244,4 +356,317 @@ typedef struct JsonPathParseResult
extern JsonPathParseResult *parsejsonpath(const char *str, int len);
+/*
+ * Evaluation of jsonpath
+ */
+
+/* External variable passed into jsonpath. */
+typedef struct JsonPathVariableEvalContext
+{
+ char *name;
+ Oid typid;
+ int32 typmod;
+ struct ExprContext *econtext;
+ struct ExprState *estate;
+ MemoryContext mcxt; /* memory context for cached value */
+ Datum value;
+ bool isnull;
+ bool evaluated;
+} JsonPathVariableEvalContext;
+
+/* Type of SQL/JSON item */
+typedef enum JsonItemType
+{
+ /* Scalar types */
+ jsiNull = jbvNull,
+ jsiString = jbvString,
+ jsiNumeric = jbvNumeric,
+ jsiBool = jbvBool,
+ /* Composite types */
+ jsiArray = jbvArray,
+ jsiObject = jbvObject,
+ /* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
+ jsiBinary = jbvBinary,
+
+ /*
+ * Virtual types.
+ *
+ * These types are used only for in-memory JSON processing and serialized
+ * into JSON strings/numbers when outputted to json/jsonb.
+ */
+ jsiDatetime = 0x20,
+ jsiDouble = 0x21
+} JsonItemType;
+
+/* SQL/JSON item */
+typedef struct JsonItem
+{
+ struct JsonItem *next;
+
+ union
+ {
+ int type; /* XXX JsonItemType */
+
+ JsonbValue jbv;
+
+ struct
+ {
+ int type;
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ int tz;
+ } datetime;
+
+ struct
+ {
+ int type;
+ double val;
+ } dbl;
+ } val;
+} JsonItem;
+
+#define JsonItemJbv(jsi) (&(jsi)->val.jbv)
+#define JsonItemBool(jsi) (JsonItemJbv(jsi)->val.boolean)
+#define JsonItemNumeric(jsi) (JsonItemJbv(jsi)->val.numeric)
+#define JsonItemNumericDatum(jsi) NumericGetDatum(JsonItemNumeric(jsi))
+#define JsonItemString(jsi) (JsonItemJbv(jsi)->val.string)
+#define JsonItemBinary(jsi) (JsonItemJbv(jsi)->val.binary)
+#define JsonItemArray(jsi) (JsonItemJbv(jsi)->val.array)
+#define JsonItemObject(jsi) (JsonItemJbv(jsi)->val.object)
+#define JsonItemDatetime(jsi) ((jsi)->val.datetime)
+#define JsonItemDouble(jsi) ((jsi)->val.dbl.val)
+#define JsonItemDoubleDatum(jsi) Float8GetDatum(JsonItemDouble(jsi))
+
+#define JsonItemGetType(jsi) ((jsi)->val.type)
+#define JsonItemIsNull(jsi) (JsonItemGetType(jsi) == jsiNull)
+#define JsonItemIsBool(jsi) (JsonItemGetType(jsi) == jsiBool)
+#define JsonItemIsNumeric(jsi) (JsonItemGetType(jsi) == jsiNumeric)
+#define JsonItemIsString(jsi) (JsonItemGetType(jsi) == jsiString)
+#define JsonItemIsBinary(jsi) (JsonItemGetType(jsi) == jsiBinary)
+#define JsonItemIsArray(jsi) (JsonItemGetType(jsi) == jsiArray)
+#define JsonItemIsObject(jsi) (JsonItemGetType(jsi) == jsiObject)
+#define JsonItemIsDatetime(jsi) (JsonItemGetType(jsi) == jsiDatetime)
+#define JsonItemIsDouble(jsi) (JsonItemGetType(jsi) == jsiDouble)
+#define JsonItemIsScalar(jsi) (IsAJsonbScalar(JsonItemJbv(jsi)) || \
+ JsonItemIsDatetime(jsi) || \
+ JsonItemIsDouble(jsi))
+#define JsonItemIsNumber(jsi) (JsonItemIsNumeric(jsi) || \
+ JsonItemIsDouble(jsi))
+
+typedef union Jsonx
+{
+ Jsonb jb;
+ Json js;
+} Jsonx;
+
+#define DatumGetJsonxP(datum, isJsonb) \
+ ((isJsonb) ? (Jsonx *) DatumGetJsonbP(datum) : (Jsonx *) DatumGetJsonP(datum))
+
+typedef JsonbContainer JsonxContainer;
+
+typedef struct JsonxIterator
+{
+ bool isJsonb;
+ union
+ {
+ JsonbIterator *jb;
+ JsonIterator *js;
+ } it;
+} JsonxIterator;
+
+/*
+ * Represents "base object" and it's "id" for .keyvalue() evaluation.
+ */
+typedef struct JsonBaseObjectInfo
+{
+ JsonxContainer *jbc;
+ int id;
+} JsonBaseObjectInfo;
+
+/*
+ * Special data structure representing stack of current items. We use it
+ * instead of regular list in order to evade extra memory allocation. These
+ * items are always allocated in local variables.
+ */
+typedef struct JsonItemStackEntry
+{
+ JsonBaseObjectInfo base;
+ JsonItem *item;
+ struct JsonItemStackEntry *parent;
+} JsonItemStackEntry;
+
+typedef JsonItemStackEntry *JsonItemStack;
+
+typedef int (*JsonPathVarCallback) (void *vars, bool isJsonb,
+ char *varName, int varNameLen,
+ JsonItem *val, JsonbValue *baseObject);
+
+typedef struct JsonLambdaArg
+{
+ struct JsonLambdaArg *next;
+ JsonItem *val;
+ const char *name;
+ int namelen;
+} JsonLambdaArg;
+
+/*
+ * Context of jsonpath execution.
+ */
+typedef struct JsonPathExecContext
+{
+ void *vars; /* variables to substitute into jsonpath */
+ JsonPathVarCallback getVar;
+ JsonLambdaArg *args; /* for lambda evaluation */
+ JsonItem *root; /* for $ evaluation */
+ JsonItemStack stack; /* for @ evaluation */
+ JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue()
+ * evaluation */
+ int lastGeneratedObjectId; /* "id" counter for .keyvalue()
+ * evaluation */
+ void **cache;
+ MemoryContext cache_mcxt;
+ int innermostArraySize; /* for LAST array index evaluation */
+ bool laxMode; /* true for "lax" mode, false for "strict"
+ * mode */
+ bool ignoreStructuralErrors; /* with "true" structural errors such
+ * as absence of required json item or
+ * unexpected json item type are
+ * ignored */
+ bool throwErrors; /* with "false" all suppressible errors are
+ * suppressed */
+ bool isJsonb;
+} JsonPathExecContext;
+
+/* strict/lax flags is decomposed into four [un]wrap/error flags */
+#define jspStrictAbsenseOfErrors(cxt) (!(cxt)->laxMode)
+#define jspAutoUnwrap(cxt) ((cxt)->laxMode)
+#define jspAutoWrap(cxt) ((cxt)->laxMode)
+#define jspIgnoreStructuralErrors(cxt) ((cxt)->ignoreStructuralErrors)
+#define jspThrowErrors(cxt) ((cxt)->throwErrors)
+
+/* Result of jsonpath predicate evaluation */
+typedef enum JsonPathBool
+{
+ jpbFalse = 0,
+ jpbTrue = 1,
+ jpbUnknown = 2
+} JsonPathBool;
+
+/* Result of jsonpath expression evaluation */
+typedef enum JsonPathExecResult
+{
+ jperOk = 0,
+ jperNotFound = 1,
+ jperError = 2
+} JsonPathExecResult;
+
+#define jperIsError(jper) ((jper) == jperError)
+
+/*
+ * List of SQL/JSON items with shortcut for single-value list.
+ */
+typedef struct JsonValueList
+{
+ JsonItem *head;
+ JsonItem *tail;
+ int length;
+} JsonValueList;
+
+typedef struct JsonValueListIterator
+{
+ JsonItem *next;
+} JsonValueListIterator;
+
+typedef struct JsonPathFuncContext
+{
+ JsonPathExecContext *cxt;
+ JsonValueList *result;
+ const char *funcname;
+ JsonItem *jb;
+ JsonItem *item;
+ JsonPathItem *args;
+ void **argscache;
+ int nargs;
+} JsonPathFuncContext;
+
+
+extern JsonItem *JsonbValueToJsonItem(JsonbValue *jbv, JsonItem *jsi);
+extern Jsonb *JsonItemToJsonb(JsonItem *jsi);
+extern Json *JsonItemToJson(JsonItem *jsi);
+extern void JsonItemFromDatum(Datum val, Oid typid, int32 typmod,
+ JsonItem *res, bool isJsonb);
+extern Datum JsonItemToJsonxDatum(JsonItem *jsi, bool isJsonb);
+extern Datum JsonbValueToJsonxDatum(JsonbValue *jbv, bool isJsonb);
+
+extern bool JsonPathExists(Datum jb, JsonPath *path,
+ List *vars, bool isJsonb, bool *error);
+extern Datum JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper,
+ bool *empty, bool *error, List *vars, bool isJsonb);
+extern JsonItem *JsonPathValue(Datum jb, JsonPath *jp, bool *empty,
+ bool *error, List *vars, bool isJsonb);
+
+extern int EvalJsonPathVar(void *vars, bool isJsonb, char *varName,
+ int varNameLen, JsonItem *val, JsonbValue *baseObject);
+
+extern const TableFuncRoutine JsonTableRoutine;
+extern const TableFuncRoutine JsonbTableRoutine;
+
+extern int JsonbType(JsonItem *jb);
+extern int JsonxArraySize(JsonItem *jb, bool isJsonb);
+
+extern JsonItem *copyJsonItem(JsonItem *src);
+extern JsonItem *JsonWrapItemInArray(JsonItem *jbv, bool isJsonb);
+extern JsonbValue *JsonWrapItemsInArray(const JsonValueList *items,
+ bool isJsonb);
+extern void JsonAppendWrappedItems(JsonValueList *found, JsonValueList *items,
+ bool isJsonb);
+
+extern void pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry,
+ JsonItem *item, JsonBaseObjectInfo *base);
+extern void popJsonItem(JsonItemStack *stack);
+
+#define JsonValueListLength(jvl) ((jvl)->length)
+#define JsonValueListIsEmpty(jvl) (!(jvl)->length)
+#define JsonValueListHead(jvl) ((jvl)->head)
+extern void JsonValueListClear(JsonValueList *jvl);
+extern void JsonValueListAppend(JsonValueList *jvl, JsonItem *jbv);
+extern List *JsonValueListGetList(JsonValueList *jvl);
+extern void JsonValueListInitIterator(const JsonValueList *jvl,
+ JsonValueListIterator *it);
+extern JsonItem *JsonValueListNext(const JsonValueList *jvl,
+ JsonValueListIterator *it);
+extern void JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2);
+extern void JsonxIteratorInit(JsonxIterator *it, JsonxContainer *jxc,
+ bool isJsonb);
+extern JsonbIteratorToken JsonxIteratorNext(JsonxIterator *it, JsonbValue *jbv,
+ bool skipNested);
+
+extern JsonPathExecResult jspExecuteItem(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonItem *jb,
+ JsonValueList *found);
+extern JsonPathExecResult jspExecuteItemNested(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonItem *jb,
+ JsonValueList *found);
+extern JsonPathExecResult jspExecuteLambda(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonItem *jb,
+ JsonValueList *found,
+ JsonItem **params, int nparams,
+ void **pcache);
+extern JsonPathBool jspCompareItems(int32 op, JsonItem *jb1, JsonItem *jb2);
+
+/* Standard error message for SQL/JSON errors */
+#define ERRMSG_JSON_ARRAY_NOT_FOUND "SQL/JSON array not found"
+#define ERRMSG_JSON_OBJECT_NOT_FOUND "SQL/JSON object not found"
+#define ERRMSG_JSON_MEMBER_NOT_FOUND "SQL/JSON member not found"
+#define ERRMSG_JSON_NUMBER_NOT_FOUND "SQL/JSON number not found"
+#define ERRMSG_JSON_SCALAR_REQUIRED "SQL/JSON scalar required"
+#define ERRMSG_MORE_THAN_ONE_JSON_ITEM "more than one SQL/JSON item"
+#define ERRMSG_SINGLETON_JSON_ITEM_REQUIRED "singleton SQL/JSON item required"
+#define ERRMSG_NON_NUMERIC_JSON_ITEM "non-numeric SQL/JSON item"
+#define ERRMSG_INVALID_JSON_SUBSCRIPT "invalid SQL/JSON subscript"
+#define ERRMSG_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION \
+ "invalid argument for SQL/JSON datetime function"
+#define ERRMSG_NO_JSON_ITEM "no SQL/JSON item"
+
#endif
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index ea16190ec3..2b2a7a8c6f 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -97,6 +97,11 @@ extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
+extern TimestampTz timestamp2timestamptz_internal(Timestamp timestamp,
+ int *tzp, bool *error);
+
+extern TimestampTz float8_timestamptz_internal(float8 seconds, bool *error);
+
extern int isoweek2j(int year, int week);
extern void isoweek2date(int woy, int *year, int *mon, int *mday);
extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index 3619706cdc..541ae0bfe5 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -46,6 +46,8 @@
'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 abae89d51b..564e4f23c8 100644
--- a/src/interfaces/ecpg/preproc/parser.c
+++ b/src/interfaces/ecpg/preproc/parser.c
@@ -84,6 +84,9 @@ filtered_base_yylex(void)
case WITH:
cur_token_length = 4;
break;
+ case WITHOUT:
+ cur_token_length = 7;
+ break;
default:
return cur_token;
}
@@ -155,8 +158,22 @@ 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;
+
}
return cur_token;
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b4577333..74ecb7c10e 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
Sun Dec 18 03:18:00 2011 PST
(1 row)
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR: date/time field value out of range: "2018-11-02 12:34:56.123456789"
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
new file mode 100644
index 0000000000..a35cf33567
--- /dev/null
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -0,0 +1,2343 @@
+select json '{"a": 12}' @? '$';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 12}' @? '1';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 12}' @? '$.a + 2';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 12}' @? '$.b + 2';
+ ?column?
+----------
+
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"b": {"a": 12}}' @? '$.*.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"b": {"a": 12}}' @? 'strict $.*.b';
+ ?column?
+----------
+
+(1 row)
+
+select json '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select json_path_query('[1]', 'strict $[1]');
+ERROR: jsonpath array subscript is out of bounds
+select json '[1]' @? 'lax $[10000000000000000]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1]' @? 'strict $[10000000000000000]';
+ ?column?
+----------
+
+(1 row)
+
+select json_path_query('[1]', 'lax $[10000000000000000]');
+ERROR: jsonpath array subscript is out of integer range
+select json_path_query('[1]', 'strict $[10000000000000000]');
+ERROR: jsonpath array subscript is out of integer range
+select json '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select json_path_query('[1]', 'strict $[1.2]');
+ERROR: jsonpath array subscript is out of bounds
+select json_path_query('{}', 'strict $[0.3]');
+ERROR: object subscript must be a string or number
+select json '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json_path_query('{}', 'strict $[1.2]');
+ERROR: object subscript must be a string or number
+select json '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json_path_query('{}', 'strict $[-2 to 3]');
+ERROR: jsonpath array accessor can only be applied to an array
+select json '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json_path_query('1', 'lax $.a');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('1', 'strict $.a');
+ERROR: jsonpath member accessor can only be applied to an object
+select json_path_query('1', 'strict $.*');
+ERROR: jsonpath wildcard member accessor can only be applied to an object
+select json_path_query('[]', 'lax $.a');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('[]', 'strict $.a');
+ERROR: jsonpath member accessor can only be applied to an object
+select json_path_query('{}', 'lax $.a');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('{}', 'strict $.a');
+ERROR: JSON object does not contain key "a"
+select json_path_query('1', 'strict $[1]');
+ERROR: jsonpath array accessor can only be applied to an array or object
+select json_path_query('1', 'strict $[*]');
+ERROR: jsonpath wildcard array accessor can only be applied to an array
+select json_path_query('[]', 'strict $[1]');
+ERROR: jsonpath array subscript is out of bounds
+select json_path_query('[]', 'strict $["a"]');
+ERROR: jsonpath array subscript is not a single numeric value
+select json_path_query('{"a": 12, "b": {"a": 13}}', '$.a');
+ json_path_query
+-----------------
+ 12
+(1 row)
+
+select json_path_query('{"a": 12, "b": {"a": 13}}', '$.b');
+ json_path_query
+-----------------
+ {"a": 13}
+(1 row)
+
+select json_path_query('{"a": 12, "b": {"a": 13}}', '$.*');
+ json_path_query
+-----------------
+ 12
+ {"a": 13}
+(2 rows)
+
+select json_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+ json_path_query
+-----------------
+ 13
+(1 row)
+
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+ json_path_query
+-----------------
+ 13
+(1 row)
+
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+ json_path_query
+-----------------
+ 13
+ 14
+(2 rows)
+
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+ json_path_query
+-----------------
+ 13
+(1 row)
+
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+ json_path_query
+-----------------
+ 13
+(1 row)
+
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+ json_path_query
+-----------------
+ 13
+(1 row)
+
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10 / 0].a');
+ERROR: division by zero
+select json_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
+ json_path_query
+-----------------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select json_path_query('1', 'lax $[0]');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('1', 'lax $[*]');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{}', 'lax $[0]');
+ json_path_query
+-----------------
+ {}
+(1 row)
+
+select json_path_query('[1]', 'lax $[0]');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('[1]', 'lax $[*]');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('[1,2,3]', 'lax $[*]');
+ json_path_query
+-----------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select json_path_query('[1,2,3]', 'strict $[*].a');
+ERROR: jsonpath member accessor can only be applied to an object
+select json_path_query('[]', '$[last]');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('[]', '$[last ? (exists(last))]');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('[]', 'strict $[last]');
+ERROR: jsonpath array subscript is out of bounds
+select json_path_query('[1]', '$[last]');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{}', '$[last]');
+ json_path_query
+-----------------
+ {}
+(1 row)
+
+select json_path_query('[1,2,3]', '$[last]');
+ json_path_query
+-----------------
+ 3
+(1 row)
+
+select json_path_query('[1,2,3]', '$[last - 1]');
+ json_path_query
+-----------------
+ 2
+(1 row)
+
+select json_path_query('[1,2,3]', '$[last ? (@.type() == "number")]');
+ json_path_query
+-----------------
+ 3
+(1 row)
+
+select json_path_query('[1,2,3]', '$[last ? (@.type() == "string")]');
+ERROR: jsonpath array subscript is not a single numeric value
+select * from json_path_query('{"a": 10}', '$');
+ json_path_query
+-----------------
+ {"a": 10}
+(1 row)
+
+select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)');
+ERROR: could not find jsonpath variable "value"
+select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '1');
+ERROR: "vars" argument is not an object
+DETAIL: Jsonpath parameters should be encoded as key-value pairs of "vars" object.
+select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '[{"value" : 13}]');
+ERROR: "vars" argument is not an object
+DETAIL: Jsonpath parameters should be encoded as key-value pairs of "vars" object.
+select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 13}');
+ json_path_query
+-----------------
+ {"a": 10}
+(1 row)
+
+select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 8}');
+ json_path_query
+-----------------
+(0 rows)
+
+select * from json_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ json_path_query
+-----------------
+ 10
+(1 row)
+
+select * from json_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ json_path_query
+-----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from json_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $x.value)', '{"x": {"value" : 13}}');
+ json_path_query
+-----------------
+ 10
+ 11
+(2 rows)
+
+select * from json_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ json_path_query
+-----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from json_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ json_path_query
+-----------------
+ "1"
+(1 row)
+
+select * from json_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ json_path_query
+-----------------
+ "1"
+(1 row)
+
+select * from json_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : null}');
+ json_path_query
+-----------------
+ null
+(1 row)
+
+select * from json_path_query('[1, "2", null]', '$[*] ? (@ != null)');
+ json_path_query
+-----------------
+ 1
+ "2"
+(2 rows)
+
+select * from json_path_query('[1, "2", null]', '$[*] ? (@ == null)');
+ json_path_query
+-----------------
+ null
+(1 row)
+
+select * from json_path_query('{}', '$ ? (@ == @)');
+ json_path_query
+-----------------
+(0 rows)
+
+select * from json_path_query('[]', 'strict $ ? (@ == @)');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**');
+ json_path_query
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{0}');
+ json_path_query
+-----------------
+ {"a": {"b": 1}}
+(1 row)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}');
+ json_path_query
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{1}');
+ json_path_query
+-----------------
+ {"b": 1}
+(1 row)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}');
+ json_path_query
+-----------------
+ {"b": 1}
+ 1
+(2 rows)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{2}');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{last}');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))');
+ json_path_query
+-----------------
+ {"x": 2}
+(1 row)
+
+select json_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');
+ json_path_query
+-----------------
+ {"x": 2}
+(1 row)
+
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? (exists (@.x))');
+ json_path_query
+-----------------
+ {"x": 2}
+(1 row)
+
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? (exists (@.x + "3"))');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? ((exists (@.x + "3")) is unknown)');
+ json_path_query
+-----------------
+ {"x": 2}
+ {"y": 3}
+(2 rows)
+
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g[*] ? (exists (@.x))');
+ json_path_query
+-----------------
+ {"x": 2}
+(1 row)
+
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g[*] ? ((exists (@.x)) is unknown)');
+ json_path_query
+-----------------
+ {"y": 3}
+(1 row)
+
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g ? (exists (@[*].x))');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g ? ((exists (@[*].x)) is unknown)');
+ json_path_query
+----------------------
+ [{"x": 2}, {"y": 3}]
+(1 row)
+
+--test ternary logic
+select
+ x, y,
+ json_path_query(
+ '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true && $y == true) ||
+ @ == false && !($x == true && $y == true) ||
+ @ == null && ($x == true && $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x && y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+ x | y | x && y
+--------+--------+--------
+ true | true | true
+ true | false | false
+ true | "null" | null
+ false | true | false
+ false | false | false
+ false | "null" | false
+ "null" | true | null
+ "null" | false | false
+ "null" | "null" | null
+(9 rows)
+
+select
+ x, y,
+ json_path_query(
+ '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true || $y == true) ||
+ @ == false && !($x == true || $y == true) ||
+ @ == null && ($x == true || $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x || y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+ x | y | x || y
+--------+--------+--------
+ true | true | true
+ true | false | true
+ true | "null" | true
+ false | true | true
+ false | false | false
+ false | "null" | null
+ "null" | true | true
+ "null" | false | null
+ "null" | "null" | null
+(9 rows)
+
+select json '{"a": 1, "b":1}' @? '$ ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b":1}}' @? '$ ? (@.a == @.b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"c": {"a": 1, "b":1}}' @? '$.c ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b":1}}' @? '$.* ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1, "b":1}' @? '$.** ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b":1}}' @? '$.** ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == 1 + 1)');
+ json_path_query
+-----------------
+ {"a": 2, "b":1}
+(1 row)
+
+select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (1 + 1))');
+ json_path_query
+-----------------
+ {"a": 2, "b":1}
+(1 row)
+
+select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == @.b + 1)');
+ json_path_query
+-----------------
+ {"a": 2, "b":1}
+(1 row)
+
+select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (@.b + 1))');
+ json_path_query
+-----------------
+ {"a": 2, "b":1}
+(1 row)
+
+select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -@.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 2, "b":1}}' @? '$.** ? (@.a == 1 - - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - +@.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- arithmetic errors
+select json_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+ json_path_query
+-----------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select json_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+ json_path_query
+-----------------
+ 0
+(1 row)
+
+select json_path_query('0', '1 / $');
+ERROR: division by zero
+select json_path_query('0', '1 / $ + 2');
+ERROR: division by zero
+select json_path_query('0', '-(3 + 1 % $)');
+ERROR: division by zero
+select json_path_query('1', '$ + "2"');
+ERROR: right operand of jsonpath operator + is not a single numeric value
+select json_path_query('[1, 2]', '3 * $');
+ERROR: right operand of jsonpath operator * is not a single numeric value
+select json_path_query('"a"', '-$');
+ERROR: operand of unary jsonpath operator - is not a numeric value
+select json_path_query('[1,"2",3]', '+$');
+ERROR: operand of unary jsonpath operator + is not a numeric value
+select json '["1",2,0,3]' @? '-$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,"2",0,3]' @? '-$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '["1",2,0,3]' @? 'strict -$[*]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1,"2",0,3]' @? 'strict -$[*]';
+ ?column?
+----------
+
+(1 row)
+
+-- unwrapping of operator arguments in lax mode
+select json_path_query('{"a": [2]}', 'lax $.a * 3');
+ json_path_query
+-----------------
+ 6
+(1 row)
+
+select json_path_query('{"a": [2]}', 'lax $.a + 3');
+ json_path_query
+-----------------
+ 5
+(1 row)
+
+select json_path_query('{"a": [2, 3, 4]}', 'lax -$.a');
+ json_path_query
+-----------------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select json_path_query('{"a": [1, 2]}', 'lax $.a * 3');
+ERROR: left operand of jsonpath operator * is not a single numeric value
+-- extension: boolean expressions
+select json_path_query('2', '$ > 1');
+ json_path_query
+-----------------
+ true
+(1 row)
+
+select json_path_query('2', '$ <= 1');
+ json_path_query
+-----------------
+ false
+(1 row)
+
+select json_path_query('2', '$ == "2"');
+ json_path_query
+-----------------
+ null
+(1 row)
+
+select json '2' @? '$ == "2"';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '2' @@ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '2' @@ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '2' @@ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select json '2' @@ '1';
+ ?column?
+----------
+
+(1 row)
+
+select json '{}' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+select json '[]' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1,2,3]' @@ '$[*]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[]' @@ '$[*]';
+ ?column?
+----------
+
+(1 row)
+
+select json_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+ json_path_match
+-----------------
+ f
+(1 row)
+
+select json_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ json_path_match
+-----------------
+ t
+(1 row)
+
+select json_path_query('[null,1,true,"a",[],{}]', '$.type()');
+ json_path_query
+-----------------
+ "array"
+(1 row)
+
+select json_path_query('[null,1,true,"a",[],{}]', 'lax $.type()');
+ json_path_query
+-----------------
+ "array"
+(1 row)
+
+select json_path_query('[null,1,true,"a",[],{}]', '$[*].type()');
+ json_path_query
+-----------------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select json_path_query('null', 'null.type()');
+ json_path_query
+-----------------
+ "null"
+(1 row)
+
+select json_path_query('null', 'true.type()');
+ json_path_query
+-----------------
+ "boolean"
+(1 row)
+
+select json_path_query('null', '123.type()');
+ json_path_query
+-----------------
+ "number"
+(1 row)
+
+select json_path_query('null', '"123".type()');
+ json_path_query
+-----------------
+ "string"
+(1 row)
+
+select json_path_query('{"a": 2}', '($.a - 5).abs() + 10');
+ json_path_query
+-----------------
+ 13
+(1 row)
+
+select json_path_query('{"a": 2.5}', '-($.a * $.a).floor() % 4.3');
+ json_path_query
+-----------------
+ -1.7
+(1 row)
+
+select json_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)');
+ json_path_query
+-----------------
+ true
+(1 row)
+
+select json_path_query('[1, 2, 3]', '($[*] > 3).type()');
+ json_path_query
+-----------------
+ "boolean"
+(1 row)
+
+select json_path_query('[1, 2, 3]', '($[*].a > 3).type()');
+ json_path_query
+-----------------
+ "boolean"
+(1 row)
+
+select json_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()');
+ json_path_query
+-----------------
+ "null"
+(1 row)
+
+select json_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
+ERROR: jsonpath item method .size() can only be applied to an array
+select json_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');
+ json_path_query
+-----------------
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1
+ 3
+ 1
+ 1
+(9 rows)
+
+select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
+ json_path_query
+-----------------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()');
+ json_path_query
+-----------------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()');
+ json_path_query
+-----------------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()');
+ json_path_query
+-----------------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()');
+ json_path_query
+-----------------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select json_path_query('[{},1]', '$[*].keyvalue()');
+ERROR: jsonpath item method .keyvalue() can only be applied to an object
+select json_path_query('{}', '$.keyvalue()');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()');
+ json_path_query
+----------------------------------------------
+ {"key": "a", "value": 1, "id": 0}
+ {"key": "b", "value": [1, 2], "id": 0}
+ {"key": "c", "value": {"a": "bbb"}, "id": 0}
+(3 rows)
+
+select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()');
+ json_path_query
+-----------------------------------------------
+ {"key": "a", "value": 1, "id": 1}
+ {"key": "b", "value": [1, 2], "id": 1}
+ {"key": "c", "value": {"a": "bbb"}, "id": 24}
+(3 rows)
+
+select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()');
+ERROR: jsonpath item method .keyvalue() can only be applied to an object
+select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()');
+ json_path_query
+-----------------------------------------------
+ {"key": "a", "value": 1, "id": 1}
+ {"key": "b", "value": [1, 2], "id": 1}
+ {"key": "c", "value": {"a": "bbb"}, "id": 24}
+(3 rows)
+
+select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue().a');
+ERROR: jsonpath item method .keyvalue() can only be applied to an object
+select json '{"a": 1, "b": [1, 2]}' @? 'lax $.keyvalue()';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1, "b": [1, 2]}' @? 'lax $.keyvalue().key';
+ ?column?
+----------
+ t
+(1 row)
+
+select json_path_query('null', '$.double()');
+ERROR: jsonpath item method .double() can only be applied to a string or numeric value
+select json_path_query('true', '$.double()');
+ERROR: jsonpath item method .double() can only be applied to a string or numeric value
+select json_path_query('[]', '$.double()');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('[]', 'strict $.double()');
+ERROR: jsonpath item method .double() can only be applied to a string or numeric value
+select json_path_query('{}', '$.double()');
+ERROR: jsonpath item method .double() can only be applied to a string or numeric value
+select json_path_query('1.23', '$.double()');
+ json_path_query
+-----------------
+ 1.23
+(1 row)
+
+select json_path_query('"1.23"', '$.double()');
+ json_path_query
+-----------------
+ 1.23
+(1 row)
+
+select json_path_query('"1.23aaa"', '$.double()');
+ERROR: jsonpath item method .double() can only be applied to a numeric value
+select json_path_query('"nan"', '$.double()');
+ json_path_query
+-----------------
+ "NaN"
+(1 row)
+
+select json_path_query('"NaN"', '$.double()');
+ json_path_query
+-----------------
+ "NaN"
+(1 row)
+
+select json_path_query('"inf"', '$.double()');
+ json_path_query
+-----------------
+ "Infinity"
+(1 row)
+
+select json_path_query('"-inf"', '$.double()');
+ json_path_query
+-----------------
+ "-Infinity"
+(1 row)
+
+select json_path_query('{}', '$.abs()');
+ERROR: jsonpath item method .abs() can only be applied to a numeric value
+select json_path_query('true', '$.floor()');
+ERROR: jsonpath item method .floor() can only be applied to a numeric value
+select json_path_query('"1.2"', '$.ceiling()');
+ERROR: jsonpath item method .ceiling() can only be applied to a numeric value
+select json_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
+ json_path_query
+-----------------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select json_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
+ json_path_query
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select json_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
+ json_path_query
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select json_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
+ json_path_query
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select json_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
+ json_path_query
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select json_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
+ json_path_query
+-----------------
+ null
+ 1
+(2 rows)
+
+select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")');
+ json_path_query
+-----------------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^a b.* c " flag "ix")');
+ json_path_query
+-----------------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "m")');
+ json_path_query
+-----------------
+ "abc"
+ "abdacb"
+ "adc\nabc"
+(3 rows)
+
+select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "s")');
+ json_path_query
+-----------------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select json_path_query('null', '$.datetime()');
+ERROR: jsonpath item method .datetime() can only be applied to a string or number
+select json_path_query('true', '$.datetime()');
+ERROR: jsonpath item method .datetime() can only be applied to a string or number
+select json_path_query('[]', '$.datetime()');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('[]', 'strict $.datetime()');
+ERROR: jsonpath item method .datetime() can only be applied to a string or number
+select json_path_query('{}', '$.datetime()');
+ERROR: jsonpath item method .datetime() can only be applied to a string or number
+select json_path_query('""', '$.datetime()');
+ERROR: unrecognized datetime format
+HINT: use datetime template argument for explicit format specification
+select json_path_query('"12:34"', '$.datetime("aaa")');
+ERROR: datetime format is not dated and not timed
+select json_path_query('"12:34"', '$.datetime("aaa", 1)');
+ERROR: datetime format is not dated and not timed
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 1)');
+ json_path_query
+---------------------
+ "12:34:00+00:00:01"
+(1 row)
+
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)');
+ERROR: timezone argument of jsonpath item method .datetime() is out of integer range
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)');
+ERROR: timezone argument of jsonpath item method .datetime() is out of integer range
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483647)');
+ json_path_query
+-------------------------
+ "12:34:00+596523:14:07"
+(1 row)
+
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483648)');
+ERROR: timezone argument of jsonpath item method .datetime() is out of integer range
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483647)');
+ json_path_query
+-------------------------
+ "12:34:00-596523:14:07"
+(1 row)
+
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483648)');
+ERROR: timezone argument of jsonpath item method .datetime() is out of integer range
+select json_path_query('"aaaa"', '$.datetime("HH24")');
+ERROR: invalid value "aa" for "HH24"
+DETAIL: Value must be an integer.
+-- Standard extension: UNIX epoch to timestamptz
+select json_path_query('0', '$.datetime()');
+ json_path_query
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select json_path_query('0', '$.datetime().type()');
+ json_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json_path_query('1490216035.5', '$.datetime()');
+ json_path_query
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
+select json '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+ ?column?
+----------
+ t
+(1 row)
+
+select json_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ json_path_query
+-----------------
+ "2017-03-10"
+(1 row)
+
+select json_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ json_path_query
+-----------------
+ "date"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ json_path_query
+-----------------
+ "2017-03-10"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ json_path_query
+-----------------
+ "date"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+ json_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+ json_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+ json_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select json_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+ json_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ json_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: missing time-zone in input string for type timestamptz
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:00"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:12"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+ json_path_query
+--------------------------------
+ "2017-03-10T12:34:00-00:12:34"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+ERROR: invalid input syntax for type timestamptz: "UTC"
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", 12 * 60)');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:12"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", -(5 * 3600 + 12 * 60 + 34))');
+ json_path_query
+--------------------------------
+ "2017-03-10T12:34:00-05:12:34"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)',
+ json_build_object('tz', extract(timezone from now())));
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:00"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select json_path_query('"12:34"', '$.datetime("HH24:MI")');
+ json_path_query
+-----------------
+ "12:34:00"
+(1 row)
+
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: missing time-zone in input string for type timetz
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+ json_path_query
+------------------
+ "12:34:00+00:00"
+(1 row)
+
+select json_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ json_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select json_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ json_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select json_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ json_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select json_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ json_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ json_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: missing time-zone in input string for type timestamptz
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00+10:00"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)',
+ json_build_object('tz', extract(timezone from now())));
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00+10:00"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select json_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select json_path_query('"12:34"', '$.datetime("HH24:MI")');
+ json_path_query
+-----------------
+ "12:34:00"
+(1 row)
+
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: missing time-zone in input string for type timetz
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+ json_path_query
+------------------
+ "12:34:00+10:00"
+(1 row)
+
+select json_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ json_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select json_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ json_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select json_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ json_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select json_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ json_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select json_path_query('"2017-03-10"', '$.datetime().type()');
+ json_path_query
+-----------------
+ "date"
+(1 row)
+
+select json_path_query('"2017-03-10"', '$.datetime()');
+ json_path_query
+-----------------
+ "2017-03-10"
+(1 row)
+
+select json_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+ json_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select json_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+ json_path_query
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select json_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+ json_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select json_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+ json_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select json_path_query('"12:34:56"', '$.datetime().type()');
+ json_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select json_path_query('"12:34:56"', '$.datetime()');
+ json_path_query
+-----------------
+ "12:34:56"
+(1 row)
+
+select json_path_query('"12:34:56 +3"', '$.datetime().type()');
+ json_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select json_path_query('"12:34:56 +3"', '$.datetime()');
+ json_path_query
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select json_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+ json_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select json_path_query('"12:34:56 +3:10"', '$.datetime()');
+ json_path_query
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select json_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ json_path_query
+-----------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+(2 rows)
+
+select json_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ json_path_query
+-----------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+(4 rows)
+
+select json_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+ json_path_query
+-----------------
+ "2017-03-09"
+(1 row)
+
+-- time comparison
+select json_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ json_path_query
+-----------------
+ "12:35:00"
+(1 row)
+
+select json_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ json_path_query
+-----------------
+ "12:35:00"
+ "12:36:00"
+(2 rows)
+
+select json_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+ json_path_query
+-----------------
+ "12:34:00"
+(1 row)
+
+-- timetz comparison
+select json_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ json_path_query
+------------------
+ "12:35:00+01:00"
+(1 row)
+
+select json_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ json_path_query
+------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+(3 rows)
+
+select json_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+ json_path_query
+------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+(2 rows)
+
+-- timestamp comparison
+select json_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ json_path_query
+-----------------------
+ "2017-03-10T12:35:00"
+(1 row)
+
+select json_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ json_path_query
+-----------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-11"
+(3 rows)
+
+select json_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ json_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10"
+(2 rows)
+
+-- timestamptz comparison
+select json_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+(1 row)
+
+select json_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+(3 rows)
+
+select json_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ json_path_query
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+(2 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT json_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
+ json_path_query
+-----------------
+ {"a": 1}
+ {"a": 2}
+(2 rows)
+
+SELECT json_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+ json_path_query
+-----------------
+(0 rows)
+
+SELECT json_path_query('[{"a": 1}, {"a": 2}]', '[$[*].a]');
+ json_path_query
+-----------------
+ [1, 2]
+(1 row)
+
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
+ERROR: JSON object does not contain key "a"
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
+ json_path_query_array
+-----------------------
+ [1, 2]
+(1 row)
+
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+ json_path_query_array
+-----------------------
+ [1]
+(1 row)
+
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+ json_path_query_array
+-----------------------
+ []
+(1 row)
+
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+ json_path_query_array
+-----------------------
+ [2, 3]
+(1 row)
+
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+ json_path_query_array
+-----------------------
+ []
+(1 row)
+
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '[$[*].a]');
+ json_path_query_array
+-----------------------
+ [[1, 2]]
+(1 row)
+
+SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
+ERROR: JSON object does not contain key "a"
+SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a');
+ json_path_query_first
+-----------------------
+ 1
+(1 row)
+
+SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+ json_path_query_first
+-----------------------
+ 1
+(1 row)
+
+SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+ json_path_query_first
+-----------------------
+
+(1 row)
+
+SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+ json_path_query_first
+-----------------------
+ 2
+(1 row)
+
+SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+ json_path_query_first
+-----------------------
+
+(1 row)
+
+SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT json_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 1, "max": 4}');
+ json_path_exists
+------------------
+ t
+(1 row)
+
+SELECT json_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 3, "max": 4}');
+ json_path_exists
+------------------
+ f
+(1 row)
+
+SELECT json '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT json '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2';
+ ?column?
+----------
+ f
+(1 row)
+
+-- extension: path sequences
+select json_path_query('[1,2,3,4,5]', '10, 20, $[*], 30');
+ json_path_query
+-----------------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select json_path_query('[1,2,3,4,5]', 'lax 10, 20, $[*].a, 30');
+ json_path_query
+-----------------
+ 10
+ 20
+ 30
+(3 rows)
+
+select json_path_query('[1,2,3,4,5]', 'strict 10, 20, $[*].a, 30');
+ERROR: jsonpath member accessor can only be applied to an object
+select json_path_query('[1,2,3,4,5]', '-(10, 20, $[1 to 3], 30)');
+ json_path_query
+-----------------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select json_path_query('[1,2,3,4,5]', 'lax (10, 20.5, $[1 to 3], "30").double()');
+ json_path_query
+-----------------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select json_path_query('[1,2,3,4,5]', '$[(0, $[*], 5) ? (@ == 3)]');
+ json_path_query
+-----------------
+ 4
+(1 row)
+
+select json_path_query('[1,2,3,4,5]', '$[(0, $[*], 3) ? (@ == 3)]');
+ERROR: jsonpath array subscript is not a single numeric value
+-- extension: array constructors
+select json_path_query('[1, 2, 3]', '[]');
+ json_path_query
+-----------------
+ []
+(1 row)
+
+select json_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5]');
+ json_path_query
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5][*]');
+ json_path_query
+-----------------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select json_path_query('[1, 2, 3]', '[(1, (2, $[*])), (4, 5)]');
+ json_path_query
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json_path_query('[1, 2, 3]', '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]');
+ json_path_query
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select json_path_query('[1, 2, 3]', 'strict [1, 2, $[*].a, 4, 5]');
+ERROR: jsonpath member accessor can only be applied to an object
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '[$[*][*] ? (@ > 3)]');
+ json_path_query
+-----------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select json_path_query('[1, 2, 3]', '{}');
+ json_path_query
+-----------------
+ {}
+(1 row)
+
+select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}');
+ json_path_query
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}.*');
+ json_path_query
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}[*]');
+ json_path_query
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": ($[*], 4, 5)}');
+ERROR: value in jsonpath object constructor must be a singleton
+select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}');
+ json_path_query
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select json '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json_path_query('{"a": 1}', '$["a"]');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('{"a": 1}', 'strict $["b"]');
+ERROR: JSON object does not contain the specified key
+select json_path_query('{"a": 1}', 'lax $["b"]');
+ json_path_query
+-----------------
+(0 rows)
+
+select json_path_query('{"a": 1, "b": 2}', 'lax $["b", "c", "b", "a", 0 to 3]');
+ json_path_query
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select json_path_query('null', '{"a": 1}["a"]');
+ json_path_query
+-----------------
+ 1
+(1 row)
+
+select json_path_query('null', '{"a": 1}["b"]');
+ json_path_query
+-----------------
+(0 rows)
+
diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regress/expected/json_sqljson.out
new file mode 100644
index 0000000000..0e9f28694f
--- /dev/null
+++ b/src/test/regress/expected/json_sqljson.out
@@ -0,0 +1,2076 @@
+-- JSON_EXISTS
+SELECT JSON_EXISTS(NULL FORMAT JSON, '$');
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS(NULL::text FORMAT JSON, '$');
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS(NULL::bytea FORMAT JSON, '$');
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS(NULL::json FORMAT JSON, '$');
+WARNING: FORMAT JSON has no effect for json and jsonb types
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS(NULL::jsonb FORMAT JSON, '$');
+WARNING: FORMAT JSON has no effect for json and jsonb types
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS(NULL::json, '$');
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS('' FORMAT JSON, '$');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS('' FORMAT JSON, '$' TRUE ON ERROR);
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS('' FORMAT JSON, '$' FALSE ON ERROR);
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS('' FORMAT JSON, '$' UNKNOWN ON ERROR);
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS('' FORMAT JSON, '$' ERROR ON ERROR);
+ERROR: invalid input syntax for type json
+DETAIL: The input string ended unexpectedly.
+CONTEXT: JSON data, line 1:
+SELECT JSON_EXISTS(bytea '' FORMAT JSON, '$' ERROR ON ERROR);
+ERROR: invalid input syntax for type json
+DETAIL: The input string ended unexpectedly.
+CONTEXT: JSON data, line 1:
+SELECT JSON_EXISTS(json '[]', '$');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS('[]' FORMAT JSON, '$');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(JSON_OBJECT(RETURNING bytea FORMAT JSON) FORMAT JSON, '$');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(json '1', '$');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(json 'null', '$');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(json '[]', '$');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(json '1', '$.a');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(json '1', 'strict $.a');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(json '1', 'strict $.a' ERROR ON ERROR);
+ERROR: jsonpath member accessor can only be applied to an object
+SELECT JSON_EXISTS(json 'null', '$.a');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(json '[]', '$.a');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(json '[1, "aaa", {"a": 1}]', 'strict $.a');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(json '[1, "aaa", {"a": 1}]', 'lax $.a');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(json '{}', '$.a');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(json '{"b": 1, "a": 2}', '$.a');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(json '1', '$.a.b');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(json '{"a": {"b": 1}}', '$.a.b');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.a.b');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING 1 AS x);
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING '1' AS x);
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 2 AS y);
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 1 AS y);
+ json_exists
+-------------
+ f
+(1 row)
+
+-- extension: boolean expressions
+SELECT JSON_EXISTS(json '1', '$ > 2');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(json '1', '$.a > 2' ERROR ON ERROR);
+ json_exists
+-------------
+ t
+(1 row)
+
+-- JSON_VALUE
+SELECT JSON_VALUE(NULL, '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(NULL FORMAT JSON, '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(NULL::text, '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(NULL::bytea, '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(NULL::json, '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(NULL::jsonb FORMAT JSON, '$');
+WARNING: FORMAT JSON has no effect for json and jsonb types
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE('' FORMAT JSON, '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE('' FORMAT JSON, '$' NULL ON ERROR);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE('' FORMAT JSON, '$' DEFAULT '"default value"' ON ERROR);
+ json_value
+-----------------
+ "default value"
+(1 row)
+
+SELECT JSON_VALUE('' FORMAT JSON, '$' ERROR ON ERROR);
+ERROR: invalid input syntax for type json
+DETAIL: The input string ended unexpectedly.
+CONTEXT: JSON data, line 1:
+SELECT JSON_VALUE(json 'null', '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(json 'null', '$' RETURNING int);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(json 'true', '$');
+ json_value
+------------
+ true
+(1 row)
+
+SELECT JSON_VALUE(json 'true', '$' RETURNING bool);
+ json_value
+------------
+ t
+(1 row)
+
+SELECT JSON_VALUE(json '123', '$');
+ json_value
+------------
+ 123
+(1 row)
+
+SELECT JSON_VALUE(json '123', '$' RETURNING int) + 234;
+ ?column?
+----------
+ 357
+(1 row)
+
+SELECT JSON_VALUE(json '123', '$' RETURNING text);
+ json_value
+------------
+ 123
+(1 row)
+
+/* jsonb bytea ??? */
+SELECT JSON_VALUE(json '123', '$' RETURNING bytea ERROR ON ERROR);
+ERROR: SQL/JSON item cannot be cast to target type
+SELECT JSON_VALUE(json '1.23', '$');
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(json '1.23', '$' RETURNING int);
+ json_value
+------------
+ 1
+(1 row)
+
+SELECT JSON_VALUE(json '"1.23"', '$' RETURNING numeric);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(json '"1.23"', '$' RETURNING int ERROR ON ERROR);
+ERROR: invalid input syntax for type integer: "1.23"
+SELECT JSON_VALUE(json '"aaa"', '$');
+ json_value
+------------
+ aaa
+(1 row)
+
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING text);
+ json_value
+------------
+ aaa
+(1 row)
+
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING char(5));
+ json_value
+------------
+ aaa
+(1 row)
+
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING char(2));
+ json_value
+------------
+ aa
+(1 row)
+
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING json);
+ json_value
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING jsonb);
+ json_value
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING json ERROR ON ERROR);
+ json_value
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING jsonb ERROR ON ERROR);
+ json_value
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_VALUE(json '"\"aaa\""', '$' RETURNING json);
+ json_value
+------------
+ "\"aaa\""
+(1 row)
+
+SELECT JSON_VALUE(json '"\"aaa\""', '$' RETURNING jsonb);
+ json_value
+------------
+ "\"aaa\""
+(1 row)
+
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING int);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING int ERROR ON ERROR);
+ERROR: invalid input syntax for type integer: "aaa"
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR);
+ json_value
+------------
+ 111
+(1 row)
+
+SELECT JSON_VALUE(json '"123"', '$' RETURNING int) + 234;
+ ?column?
+----------
+ 357
+(1 row)
+
+SELECT JSON_VALUE(json '"2017-02-20"', '$' RETURNING date) + 9;
+ ?column?
+------------
+ 03-01-2017
+(1 row)
+
+-- Test NULL checks execution in domain types
+CREATE DOMAIN sqljson_int_not_null AS int NOT NULL;
+SELECT JSON_VALUE(json '1', '$.a' RETURNING sqljson_int_not_null);
+ERROR: domain sqljson_int_not_null does not allow null values
+SELECT JSON_VALUE(json '1', '$.a' RETURNING sqljson_int_not_null NULL ON ERROR);
+ERROR: domain sqljson_int_not_null does not allow null values
+SELECT JSON_VALUE(json '1', '$.a' RETURNING sqljson_int_not_null DEFAULT NULL ON ERROR);
+ERROR: domain sqljson_int_not_null does not allow null values
+SELECT JSON_VALUE(json '[]', '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(json '[]', '$' ERROR ON ERROR);
+ERROR: JSON path expression in JSON_VALUE should return singleton scalar item
+SELECT JSON_VALUE(json '{}', '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(json '{}', '$' ERROR ON ERROR);
+ERROR: JSON path expression in JSON_VALUE should return singleton scalar item
+SELECT JSON_VALUE(json '1', '$.a');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(json '1', 'strict $.a' ERROR ON ERROR);
+ERROR: jsonpath member accessor can only be applied to an object
+SELECT JSON_VALUE(json '1', 'strict $.a' DEFAULT 'error' ON ERROR);
+ json_value
+------------
+ error
+(1 row)
+
+SELECT JSON_VALUE(json '1', 'lax $.a' ERROR ON ERROR);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(json '1', 'lax $.a' ERROR ON EMPTY ERROR ON ERROR);
+ERROR: no SQL/JSON item
+SELECT JSON_VALUE(json '1', 'strict $.a' DEFAULT 2 ON ERROR);
+ json_value
+------------
+ 2
+(1 row)
+
+SELECT JSON_VALUE(json '1', 'lax $.a' DEFAULT 2 ON ERROR);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(json '1', 'lax $.a' DEFAULT '2' ON ERROR);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(json '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(json '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR);
+ json_value
+------------
+ 2
+(1 row)
+
+SELECT JSON_VALUE(json '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR);
+ json_value
+------------
+ 3
+(1 row)
+
+SELECT JSON_VALUE(json '[1,2]', '$[*]' ERROR ON ERROR);
+ERROR: JSON path expression in JSON_VALUE should return singleton scalar item
+SELECT JSON_VALUE(json '[1,2]', '$[*]' DEFAULT '0' ON ERROR);
+ json_value
+------------
+ 0
+(1 row)
+
+SELECT JSON_VALUE(json '[" "]', '$[*]' RETURNING int ERROR ON ERROR);
+ERROR: invalid input syntax for type integer: " "
+SELECT JSON_VALUE(json '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR);
+ json_value
+------------
+ 5
+(1 row)
+
+SELECT JSON_VALUE(json '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR);
+ json_value
+------------
+ 1
+(1 row)
+
+SELECT
+ x,
+ JSON_VALUE(
+ json '{"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(json 'null', '$a' PASSING point ' (1, 2 )' AS a);
+ json_value
+------------
+ (1,2)
+(1 row)
+
+SELECT JSON_VALUE(json 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point);
+ json_value
+------------
+ (1,2)
+(1 row)
+
+-- Test timestamptz passing and output
+SELECT JSON_VALUE(json '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(json '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(json '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(json '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(json '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 FORMAT JSON, '$'),
+ JSON_QUERY(js FORMAT JSON, '$' WITHOUT WRAPPER),
+ JSON_QUERY(js FORMAT JSON, '$' WITH CONDITIONAL WRAPPER),
+ JSON_QUERY(js FORMAT JSON, '$' WITH UNCONDITIONAL ARRAY WRAPPER),
+ JSON_QUERY(js FORMAT JSON, '$' WITH ARRAY WRAPPER)
+FROM
+ (VALUES
+ ('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 FORMAT JSON, 'strict $[*]') AS "unspec",
+ JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITHOUT WRAPPER) AS "without",
+ JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond",
+ JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond",
+ JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITH ARRAY WRAPPER) AS "with"
+FROM
+ (VALUES
+ ('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('"aaa"' FORMAT JSON, '$' RETURNING text);
+ json_query
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text KEEP QUOTES);
+ json_query
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text KEEP QUOTES ON SCALAR STRING);
+ json_query
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text OMIT QUOTES);
+ json_query
+------------
+ aaa
+(1 row)
+
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text OMIT QUOTES ON SCALAR STRING);
+ json_query
+------------
+ aaa
+(1 row)
+
+SELECT JSON_QUERY('"aaa"' FORMAT 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('"aaa"' FORMAT JSON, '$' 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('"aaa"' FORMAT JSON, '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR);
+ json_query
+------------
+ \x616161
+(1 row)
+
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING bytea FORMAT JSONB 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(json '[1]', '$' WITH WRAPPER OMIT QUOTES);
+ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used
+LINE 1: SELECT JSON_QUERY(json '[1]', '$' WITH WRAPPER OMIT QUOTES);
+ ^
+SELECT JSON_QUERY(json '[1]', '$' WITH WRAPPER KEEP QUOTES);
+ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used
+LINE 1: SELECT JSON_QUERY(json '[1]', '$' WITH WRAPPER KEEP QUOTES);
+ ^
+SELECT JSON_QUERY(json '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTES);
+ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used
+LINE 1: ...ON_QUERY(json '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTE...
+ ^
+SELECT JSON_QUERY(json '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES);
+ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used
+LINE 1: ...ON_QUERY(json '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTE...
+ ^
+-- Should succeed
+SELECT JSON_QUERY(json '[1]', '$' WITHOUT WRAPPER OMIT QUOTES);
+ json_query
+------------
+ [1]
+(1 row)
+
+SELECT JSON_QUERY(json '[1]', '$' WITHOUT WRAPPER KEEP QUOTES);
+ json_query
+------------
+ [1]
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]');
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' NULL ON EMPTY);
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' EMPTY ARRAY ON EMPTY);
+ json_query
+------------
+ []
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' EMPTY OBJECT ON EMPTY);
+ json_query
+------------
+ {}
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY);
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY NULL ON ERROR);
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR);
+ json_query
+------------
+ []
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR);
+ json_query
+------------
+ {}
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY ERROR ON ERROR);
+ERROR: no SQL/JSON item
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON ERROR);
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY('[1,2]' FORMAT JSON, '$[*]' 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(json '[1,2]', '$' RETURNING json);
+ json_query
+------------
+ [1,2]
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING json FORMAT JSON);
+ json_query
+------------
+ [1,2]
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING jsonb);
+ json_query
+------------
+ [1, 2]
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING jsonb FORMAT JSON);
+ json_query
+------------
+ [1, 2]
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text);
+ json_query
+------------
+ [1,2]
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING char(10));
+ json_query
+------------
+ [1,2]
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING char(3));
+ json_query
+------------
+ [1,
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text FORMAT JSON);
+ json_query
+------------
+ [1,2]
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text FORMAT JSONB);
+ json_query
+------------
+ [1,2]
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea);
+ json_query
+--------------
+ \x5b312c325d
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea FORMAT JSON);
+ json_query
+--------------
+ \x5b312c325d
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea FORMAT JSONB);
+ json_query
+------------------------------------------------------------
+ \x02000040080000900800001020000000008001002000000000800200
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR);
+ json_query
+------------
+ \x7b7d
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT ON ERROR);
+ json_query
+------------
+ \x7b7d
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea FORMAT JSONB EMPTY OBJECT ON ERROR);
+ json_query
+------------
+ \x00000020
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR);
+ json_query
+------------
+ {}
+(1 row)
+
+SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING jsonb EMPTY OBJECT ON ERROR);
+ json_query
+------------
+ {}
+(1 row)
+
+SELECT
+ x, y,
+ JSON_QUERY(
+ json '[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 sqljson_rec AS (a int, t text, js json, jb jsonb, jsa json[]);
+CREATE TYPE sqljson_reca AS (reca sqljson_rec[]);
+SELECT JSON_QUERY(json '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljson_rec);
+ json_query
+-----------------------------------------------------
+ (1,aaa,"[1, ""2"", {}]","{""x"": [1, ""2"", {}]}",)
+(1 row)
+
+SELECT * FROM unnest((JSON_QUERY(json '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljson_rec)).jsa);
+ unnest
+------------------------
+ {"a": 1, "b": ["foo"]}
+ {"a": 2, "c": {}}
+ 123
+(3 rows)
+
+SELECT * FROM unnest((JSON_QUERY(json '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljson_reca)).reca);
+ a | t | js | jb | jsa
+---+-------------+----+------------+-----
+ 1 | ["foo", []] | | |
+ 2 | | | [{}, true] |
+(2 rows)
+
+-- Extension: array types returning
+SELECT JSON_QUERY(json '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER);
+ json_query
+--------------
+ {1,2,NULL,3}
+(1 row)
+
+SELECT * FROM unnest(JSON_QUERY(json '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljson_rec[]));
+ a | t | js | jb | jsa
+---+-------------+----+------------+-----
+ 1 | ["foo", []] | | |
+ 2 | | | [{}, true] |
+(2 rows)
+
+-- Extension: domain types returning
+SELECT JSON_QUERY(json '{"a": 1}', '$.a' RETURNING sqljson_int_not_null);
+ json_query
+------------
+ 1
+(1 row)
+
+SELECT JSON_QUERY(json '{"a": 1}', '$.b' RETURNING sqljson_int_not_null);
+ERROR: domain sqljson_int_not_null does not allow null values
+-- Test timestamptz passing and output
+SELECT JSON_QUERY(json '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(json '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(json '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_json_constraints (
+ js text,
+ i int,
+ x jsonb DEFAULT JSON_QUERY(json '[1,2]', '$[*]' WITH WRAPPER)
+ CONSTRAINT test_json_constraint1
+ CHECK (js IS JSON)
+ CONSTRAINT test_json_constraint2
+ CHECK (JSON_EXISTS(js FORMAT JSON, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr))
+ CONSTRAINT test_json_constraint3
+ CHECK (JSON_VALUE(js::json, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i)
+ CONSTRAINT test_json_constraint4
+ CHECK (JSON_QUERY(js FORMAT JSON, '$.a' RETURNING jsonb WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]')
+ CONSTRAINT test_json_constraint5
+ CHECK (JSON_QUERY(js FORMAT JSON, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a')
+);
+\d test_json_constraints
+ Table "public.test_json_constraints"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------------------------------------------------------------------------------------------------------
+ js | text | | |
+ i | integer | | |
+ x | jsonb | | | JSON_QUERY('[1,2]'::json, '$[*]' RETURNING json WITH UNCONDITIONAL WRAPPER NULL ON EMPTY NULL ON ERROR)
+Check constraints:
+ "test_json_constraint1" CHECK (js IS JSON)
+ "test_json_constraint2" CHECK (JSON_EXISTS(js FORMAT JSON, '$."a"' PASSING i + 5 AS int, i::text AS txt, ARRAY[1, 2, 3] AS arr FALSE ON ERROR))
+ "test_json_constraint3" CHECK (JSON_VALUE(js::json, '$."a"' RETURNING integer DEFAULT ('12'::text || i)::integer ON EMPTY ERROR ON ERROR) > i)
+ "test_json_constraint4" CHECK (JSON_QUERY(js FORMAT JSON, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb)
+ "test_json_constraint5" CHECK (JSON_QUERY(js FORMAT JSON, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar)
+
+SELECT check_clause
+FROM information_schema.check_constraints
+WHERE constraint_name LIKE 'test_json_constraint%';
+ check_clause
+--------------------------------------------------------------------------------------------------------------------------------------
+ ((js IS JSON))
+ (JSON_EXISTS(js FORMAT JSON, '$."a"' PASSING (i + 5) AS int, (i)::text AS txt, ARRAY[1, 2, 3] AS arr FALSE ON ERROR))
+ ((JSON_VALUE((js)::json, '$."a"' RETURNING integer DEFAULT (('12'::text || i))::integer ON EMPTY ERROR ON ERROR) > i))
+ ((JSON_QUERY(js FORMAT JSON, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb))
+ ((JSON_QUERY(js FORMAT JSON, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar))
+(5 rows)
+
+SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'test_json_constraints'::regclass;
+ pg_get_expr
+---------------------------------------------------------------------------------------------------------
+ JSON_QUERY('[1,2]'::json, '$[*]' RETURNING json WITH UNCONDITIONAL WRAPPER NULL ON EMPTY NULL ON ERROR)
+(1 row)
+
+INSERT INTO test_json_constraints VALUES ('', 1);
+ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint1"
+DETAIL: Failing row contains (, 1, [1, 2]).
+INSERT INTO test_json_constraints VALUES ('1', 1);
+ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint2"
+DETAIL: Failing row contains (1, 1, [1, 2]).
+INSERT INTO test_json_constraints VALUES ('[]');
+ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint2"
+DETAIL: Failing row contains ([], null, [1, 2]).
+INSERT INTO test_json_constraints VALUES ('{"b": 1}', 1);
+ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint2"
+DETAIL: Failing row contains ({"b": 1}, 1, [1, 2]).
+INSERT INTO test_json_constraints VALUES ('{"a": 1}', 1);
+ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint3"
+DETAIL: Failing row contains ({"a": 1}, 1, [1, 2]).
+INSERT INTO test_json_constraints VALUES ('{"a": 7}', 1);
+ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint5"
+DETAIL: Failing row contains ({"a": 7}, 1, [1, 2]).
+INSERT INTO test_json_constraints VALUES ('{"a": 10}', 1);
+ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint4"
+DETAIL: Failing row contains ({"a": 10}, 1, [1, 2]).
+DROP TABLE test_json_constraints;
+-- JSON_TABLE
+-- Should fail (JSON_TABLE can be used only in FROM clause)
+SELECT JSON_TABLE('[]', '$');
+ERROR: syntax error at or near "("
+LINE 1: SELECT JSON_TABLE('[]', '$');
+ ^
+-- Should fail (no columns)
+SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ());
+ERROR: syntax error at or near ")"
+LINE 1: SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ());
+ ^
+-- NULL => empty table
+SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS (foo int)) bar;
+ foo
+-----
+(0 rows)
+
+-- invalid json => empty table
+SELECT * FROM JSON_TABLE('', '$' COLUMNS (foo int)) bar;
+ foo
+-----
+(0 rows)
+
+SELECT * FROM JSON_TABLE('' FORMAT JSON, '$' COLUMNS (foo int)) bar;
+ foo
+-----
+(0 rows)
+
+-- invalid json => error
+SELECT * FROM JSON_TABLE('' FORMAT JSON, '$' COLUMNS (foo int) ERROR ON ERROR) bar;
+ERROR: invalid input syntax for type json
+DETAIL: The input string ended unexpectedly.
+CONTEXT: JSON data, line 1:
+--
+SELECT * FROM JSON_TABLE('123' FORMAT JSON, '$'
+ COLUMNS (item int PATH '$', foo int)) bar;
+ item | foo
+------+-----
+ 123 |
+(1 row)
+
+SELECT * FROM JSON_TABLE(json '123', '$'
+ COLUMNS (item int PATH '$', foo int)) bar;
+ item | foo
+------+-----
+ 123 |
+(1 row)
+
+-- JSON_TABLE: basic functionality
+SELECT *
+FROM
+ (VALUES
+ ('1'),
+ ('[]'),
+ ('{}'),
+ ('[1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]'),
+ ('err')
+ ) vals(js)
+ LEFT OUTER JOIN
+-- JSON_TABLE is implicitly lateral
+ JSON_TABLE(
+ vals.js FORMAT json, 'lax $[*]'
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY, -- allowed additional ordinality columns
+ "int" int PATH '$',
+ "text" text PATH '$',
+ "char(4)" char(4) PATH '$',
+ "bool" bool PATH '$',
+ "numeric" numeric PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc char(4) FORMAT JSON PATH '$',
+ jsv varchar(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSONB PATH '$',
+ aaa int, -- implicit path '$."aaa"',
+ aaa1 int PATH '$.aaa'
+ )
+ ) jt
+ ON true;
+ js | id | id2 | int | text | char(4) | bool | numeric | js | jb | jst | jsc | jsv | jsb | aaa | aaa1
+--------------------------------------------------------------------------------+----+-----+-----+---------+---------+------+---------+-----------+-----------+--------------+------+------+--------------+-----+------
+ 1 | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |
+ [] | | | | | | | | | | | | | | |
+ {} | 1 | 1 | | | | | | | | {} | {} | {} | {} | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 2 | 2 | 1 | 1.23 | 1.23 | | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 3 | 3 | 2 | 2 | 2 | | 2 | "2" | "2" | "2" | "2" | "2" | "2" | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 4 | 4 | | aaaaaaa | aaaa | | | "aaaaaaa" | "aaaaaaa" | "aaaaaaa" | "aaa | "aaa | "aaaaaaa" | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 5 | 5 | | | | | | | | null | null | null | null | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 6 | 6 | 0 | false | fals | f | | false | false | false | fals | fals | false | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 7 | 7 | 1 | true | true | t | | true | true | true | true | true | true | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 8 | 8 | | | | | | | | {"aaa": 123} | {"aa | {"aa | {"aaa": 123} | 123 | 123
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 9 | 9 | | [1,2] | [1,2 | | | "[1,2]" | "[1,2]" | "[1,2]" | "[1, | "[1, | "[1,2]" | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 10 | 10 | | "str" | "str | | | "\"str\"" | "\"str\"" | "\"str\"" | "\"s | "\"s | "\"str\"" | |
+ err | | | | | | | | | | | | | | |
+(14 rows)
+
+-- JSON_TABLE: Test backward parsing
+CREATE VIEW json_table_view AS
+SELECT * FROM
+ JSON_TABLE(
+ 'null' FORMAT JSON, 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY, -- allowed additional ordinality columns
+ "int" int PATH '$',
+ "text" text PATH '$',
+ "char(4)" char(4) PATH '$',
+ "bool" bool PATH '$',
+ "numeric" numeric PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc char(4) FORMAT JSON PATH '$',
+ jsv varchar(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSONB PATH '$',
+ aaa int, -- implicit path '$."aaa"',
+ aaa1 int PATH '$.aaa',
+ NESTED PATH '$[1]' AS p1 COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' AS "p1 1" COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' AS p2 COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1" COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' AS p22 COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
+\sv json_table_view
+CREATE OR REPLACE VIEW public.json_table_view AS
+ SELECT "json_table".id,
+ "json_table".id2,
+ "json_table"."int",
+ "json_table".text,
+ "json_table"."char(4)",
+ "json_table".bool,
+ "json_table"."numeric",
+ "json_table".js,
+ "json_table".jb,
+ "json_table".jst,
+ "json_table".jsc,
+ "json_table".jsv,
+ "json_table".jsb,
+ "json_table".aaa,
+ "json_table".aaa1,
+ "json_table".a1,
+ "json_table".b1,
+ "json_table".a11,
+ "json_table".a21,
+ "json_table".a22
+ FROM JSON_TABLE(
+ 'null'::text FORMAT JSON, '$[*]' AS json_table_path_1
+ PASSING
+ 1 + 2 AS a,
+ '"foo"'::json AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY,
+ "int" integer PATH '$',
+ text text PATH '$',
+ "char(4)" character(4) PATH '$',
+ bool boolean PATH '$',
+ "numeric" numeric PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc character(4) FORMAT JSON PATH '$',
+ jsv character varying(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSON PATH '$',
+ aaa integer PATH '$."aaa"',
+ aaa1 integer PATH '$."aaa"',
+ NESTED PATH '$[1]' AS p1
+ COLUMNS (
+ a1 integer PATH '$."a1"',
+ b1 text PATH '$."b1"',
+ NESTED PATH '$[*]' AS "p1 1"
+ COLUMNS (
+ a11 text PATH '$."a11"'
+ )
+ ),
+ NESTED PATH '$[2]' AS p2
+ COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1"
+ COLUMNS (
+ a21 text PATH '$."a21"'
+ ),
+ NESTED PATH '$[*]' AS p22
+ COLUMNS (
+ a22 text PATH '$."a22"'
+ )
+ )
+ )
+ PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22))))
+ )
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM json_table_view;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Table Function Scan on "json_table"
+ Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".aaa, "json_table".aaa1, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
+ Table Function Call: JSON_TABLE('null'::text FORMAT JSON, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::json AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb FORMAT JSON PATH '$', aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))))
+(3 rows)
+
+-- JSON_TABLE: ON EMPTY/ON ERROR behavior
+SELECT *
+FROM
+ (VALUES ('1'), ('err'), ('"err"')) vals(js),
+ JSON_TABLE(vals.js FORMAT JSON, '$' COLUMNS (a int PATH '$')) jt;
+ js | a
+-------+---
+ 1 | 1
+ "err" |
+(2 rows)
+
+SELECT *
+FROM
+ (VALUES ('1'), ('err'), ('"err"')) vals(js)
+ LEFT OUTER JOIN
+ JSON_TABLE(vals.js FORMAT JSON, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt
+ ON true;
+ERROR: invalid input syntax for type json
+DETAIL: Token "err" is invalid.
+CONTEXT: JSON data, line 1: err
+SELECT *
+FROM
+ (VALUES ('1'), ('err'), ('"err"')) vals(js)
+ LEFT OUTER JOIN
+ JSON_TABLE(vals.js FORMAT JSON, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
+ ON true;
+ERROR: invalid input syntax for type integer: "err"
+SELECT * FROM JSON_TABLE('1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
+ a
+---
+
+(1 row)
+
+SELECT * FROM JSON_TABLE('1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: jsonpath member accessor can only be applied to an object
+SELECT * FROM JSON_TABLE('1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: no SQL/JSON item
+SELECT * FROM JSON_TABLE(json '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(json '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(json '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 1
+(1 row)
+
+-- JSON_TABLE: nested paths and plans
+-- Should fail (JSON_TABLE columns shall contain explicit AS path
+-- specifications if explicit PLAN clause is used)
+SELECT * FROM JSON_TABLE(
+ json '[]', '$' -- AS required here
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 2: json '[]', '$'
+ ^
+DETAIL: JSON_TABLE columns shall contain explicit AS pathname specification if explicit PLAN clause is used
+SELECT * FROM JSON_TABLE(
+ json '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS ( -- AS required here
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 4: NESTED PATH '$' COLUMNS (
+ ^
+DETAIL: JSON_TABLE columns shall contain explicit AS pathname specification if explicit PLAN clause is used
+-- Should fail (column names anf path names shall be distinct)
+SELECT * FROM JSON_TABLE(
+ json '[]', '$' AS a
+ COLUMNS (
+ a int
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column name: a
+HINT: JSON_TABLE path names and column names shall be distinct from one another
+SELECT * FROM JSON_TABLE(
+ json '[]', '$' AS a
+ COLUMNS (
+ b int,
+ NESTED PATH '$' AS a
+ COLUMNS (
+ c int
+ )
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column name: a
+HINT: JSON_TABLE path names and column names shall be distinct from one another
+SELECT * FROM JSON_TABLE(
+ json '[]', '$'
+ COLUMNS (
+ b int,
+ NESTED PATH '$' AS b
+ COLUMNS (
+ c int
+ )
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column name: b
+HINT: JSON_TABLE path names and column names shall be distinct from one another
+SELECT * FROM JSON_TABLE(
+ json '[]', '$'
+ COLUMNS (
+ NESTED PATH '$' AS a
+ COLUMNS (
+ b int
+ ),
+ NESTED PATH '$'
+ COLUMNS (
+ NESTED PATH '$' AS a
+ COLUMNS (
+ c int
+ )
+ )
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column name: a
+HINT: JSON_TABLE path names and column names shall be distinct from one another
+-- JSON_TABLE: plan validation
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p1)
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 12: PLAN (p1)
+ ^
+DETAIL: path name mismatch: expected p0 but p1 is given
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0)
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: plan node for nested path p1 was not found in plan
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER p3)
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: plan node for nested path p1 was not found in plan
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 UNION p1 UNION p11)
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 12: PLAN (p0 UNION p1 UNION p11)
+ ^
+DETAIL: expected INNER or OUTER JSON_TABLE plan node
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p13))
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 8: NESTED PATH '$' AS p2 COLUMNS (
+ ^
+DETAIL: plan node for nested path p2 was not found in plan
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 5: NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ ^
+DETAIL: plan node for nested path p11 was not found in plan
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 12: PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+ ^
+DETAIL: plan node contains some extra or duplicate sibling nodes
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER p11) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 6: NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ^
+DETAIL: plan node for nested path p12 was not found in plan
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 9: NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ ^
+DETAIL: plan node for nested path p21 was not found in plan
+SELECT * FROM JSON_TABLE(
+ json 'null', 'strict $[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)))
+) jt;
+ bar | foo | baz
+-----+-----+-----
+(0 rows)
+
+SELECT * FROM JSON_TABLE(
+ json 'null', 'strict $[*]' -- without root path name
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 2: json 'null', 'strict $[*]'
+ ^
+DETAIL: JSON_TABLE columns shall contain explicit AS pathname specification if explicit PLAN clause is used
+-- JSON_TABLE: plan execution
+CREATE TEMP TABLE json_table_test (js text);
+INSERT INTO json_table_test
+VALUES (
+ '[
+ {"a": 1, "b": [], "c": []},
+ {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]},
+ {"a": 3, "b": [1, 2], "c": []},
+ {"x": "4", "b": [1, 2], "c": 123}
+ ]'
+);
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-- default plan (outer, union)
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-- specific plan (p outer (pb union pc))
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb union pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-- specific plan (p outer (pc union pb))
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pc union pb))
+ ) jt;
+ n | a | c | b
+---+----+----+---
+ 1 | 1 | |
+ 2 | 2 | 10 |
+ 2 | 2 | |
+ 2 | 2 | 20 |
+ 2 | 2 | | 1
+ 2 | 2 | | 2
+ 2 | 2 | | 3
+ 3 | 3 | | 1
+ 3 | 3 | | 2
+ 4 | -1 | | 1
+ 4 | -1 | | 2
+(11 rows)
+
+-- default plan (inner, union)
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(10 rows)
+
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb union pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(10 rows)
+
+-- default plan (inner, cross)
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (cross, inner)
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+(9 rows)
+
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb cross pc))
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+(9 rows)
+
+-- default plan (outer, cross)
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb cross pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+select
+ jt.*, b1 + 100 as b
+from
+ json_table (json
+ '[
+ {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]},
+ {"a": 2, "b": [10, 20], "c": [1, null, 2]},
+ {"x": "3", "b": [11, 22, 33, 44]}
+ ]',
+ '$[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on error,
+ nested path 'strict $.b[*]' as pb columns (
+ b text format json path '$',
+ nested path 'strict $[*]' as pb1 columns (
+ b1 int path '$'
+ )
+ ),
+ nested path 'strict $.c[*]' as pc columns (
+ c text format json path '$',
+ nested path 'strict $[*]' as pc1 columns (
+ c1 int path '$'
+ )
+ )
+ )
+ --plan default(outer, cross)
+ plan(p outer ((pb inner pb1) cross (pc outer pc1)))
+ ) jt;
+ n | a | b | b1 | c | c1 | b
+---+---+--------------+-----+------+----+-----
+ 1 | 1 | [1, 10] | 1 | 1 | | 101
+ 1 | 1 | [1, 10] | 1 | null | | 101
+ 1 | 1 | [1, 10] | 1 | 2 | | 101
+ 1 | 1 | [1, 10] | 10 | 1 | | 110
+ 1 | 1 | [1, 10] | 10 | null | | 110
+ 1 | 1 | [1, 10] | 10 | 2 | | 110
+ 1 | 1 | [2] | 2 | 1 | | 102
+ 1 | 1 | [2] | 2 | null | | 102
+ 1 | 1 | [2] | 2 | 2 | | 102
+ 1 | 1 | [3, 30, 300] | 3 | 1 | | 103
+ 1 | 1 | [3, 30, 300] | 3 | null | | 103
+ 1 | 1 | [3, 30, 300] | 3 | 2 | | 103
+ 1 | 1 | [3, 30, 300] | 30 | 1 | | 130
+ 1 | 1 | [3, 30, 300] | 30 | null | | 130
+ 1 | 1 | [3, 30, 300] | 30 | 2 | | 130
+ 1 | 1 | [3, 30, 300] | 300 | 1 | | 400
+ 1 | 1 | [3, 30, 300] | 300 | null | | 400
+ 1 | 1 | [3, 30, 300] | 300 | 2 | | 400
+ 2 | 2 | | | | |
+ 3 | | | | | |
+(20 rows)
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(json
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >= $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+ x | y | y | z
+---+---+-----------+---
+ 2 | 1 | [1,2,3] | 1
+ 2 | 1 | [1,2,3] | 2
+ 2 | 1 | [1,2,3] | 3
+ 3 | 1 | [1,2,3] | 1
+ 3 | 1 | [1,2,3] | 2
+ 3 | 1 | [1,2,3] | 3
+ 3 | 1 | [2,3,4,5] | 2
+ 3 | 1 | [2,3,4,5] | 3
+ 3 | 1 | [2,3,4,5] | 4
+ 3 | 1 | [2,3,4,5] | 5
+ 4 | 1 | [1,2,3] | 1
+ 4 | 1 | [1,2,3] | 2
+ 4 | 1 | [1,2,3] | 3
+ 4 | 1 | [2,3,4,5] | 2
+ 4 | 1 | [2,3,4,5] | 3
+ 4 | 1 | [2,3,4,5] | 4
+ 4 | 1 | [2,3,4,5] | 5
+ 4 | 1 | [3,4,5,6] | 3
+ 4 | 1 | [3,4,5,6] | 4
+ 4 | 1 | [3,4,5,6] | 5
+ 4 | 1 | [3,4,5,6] | 6
+ 2 | 2 | [1,2,3] | 2
+ 2 | 2 | [1,2,3] | 3
+ 3 | 2 | [1,2,3] | 2
+ 3 | 2 | [1,2,3] | 3
+ 3 | 2 | [2,3,4,5] | 2
+ 3 | 2 | [2,3,4,5] | 3
+ 3 | 2 | [2,3,4,5] | 4
+ 3 | 2 | [2,3,4,5] | 5
+ 4 | 2 | [1,2,3] | 2
+ 4 | 2 | [1,2,3] | 3
+ 4 | 2 | [2,3,4,5] | 2
+ 4 | 2 | [2,3,4,5] | 3
+ 4 | 2 | [2,3,4,5] | 4
+ 4 | 2 | [2,3,4,5] | 5
+ 4 | 2 | [3,4,5,6] | 3
+ 4 | 2 | [3,4,5,6] | 4
+ 4 | 2 | [3,4,5,6] | 5
+ 4 | 2 | [3,4,5,6] | 6
+ 2 | 3 | [1,2,3] | 3
+ 3 | 3 | [1,2,3] | 3
+ 3 | 3 | [2,3,4,5] | 3
+ 3 | 3 | [2,3,4,5] | 4
+ 3 | 3 | [2,3,4,5] | 5
+ 4 | 3 | [1,2,3] | 3
+ 4 | 3 | [2,3,4,5] | 3
+ 4 | 3 | [2,3,4,5] | 4
+ 4 | 3 | [2,3,4,5] | 5
+ 4 | 3 | [3,4,5,6] | 3
+ 4 | 3 | [3,4,5,6] | 4
+ 4 | 3 | [3,4,5,6] | 5
+ 4 | 3 | [3,4,5,6] | 6
+(52 rows)
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ json '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+ERROR: could not find jsonpath variable "x"
+-- Extension: non-constant JSON path
+SELECT JSON_EXISTS(json '{"a": 123}', '$' || '.' || 'a');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_VALUE(json '{"a": 123}', '$' || '.' || 'a');
+ json_value
+------------
+ 123
+(1 row)
+
+SELECT JSON_VALUE(json '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY);
+ json_value
+------------
+ foo
+(1 row)
+
+SELECT JSON_QUERY(json '{"a": 123}', '$' || '.' || 'a');
+ json_query
+------------
+ 123
+(1 row)
+
+SELECT JSON_QUERY(json '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
+ json_query
+------------
+ [123]
+(1 row)
+
+-- Should fail (invalid path)
+SELECT JSON_QUERY(json '{"a": 123}', 'error' || ' ' || 'error');
+ERROR: syntax error, unexpected IDENT_P, expecting $end at or near "error" of jsonpath input
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(json '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
+ERROR: only string constants supported in JSON_TABLE path specification
+LINE 1: SELECT * FROM JSON_TABLE(json '{"a": 123}', '$' || '.' || 'a...
+ ^
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 469079c5d8..13ce699a12 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4930,3 +4930,24 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
12345
(1 row)
+-- test jsonb to/from bytea conversion
+SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea;
+ bytea
+------------------------------------------------------------------------------------------------------------
+ \x0200002001000080010000000a000010140000506162000020000000008001000200004008000090000000302000000000800200
+(1 row)
+
+SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea::jsonb;
+ jsonb
+--------------------------
+ {"a": 1, "b": [2, true]}
+(1 row)
+
+SELECT 'aaaa'::bytea::jsonb;
+ERROR: incorrect jsonb binary data format
+SELECT count(*) FROM testjsonb WHERE j::bytea::jsonb <> j;
+ count
+-------
+ 0
+(1 row)
+
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index b486fb602a..6c7c3ffa44 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -177,6 +177,32 @@ select jsonb '[1]' @? 'strict $[1.2]';
(1 row)
+select jsonb_path_query('[1]', 'strict $[1.2]');
+ERROR: jsonpath array subscript is out of bounds
+select jsonb_path_query('{}', 'strict $[0.3]');
+ERROR: object subscript must be a string or number
+select jsonb '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{}', 'strict $[1.2]');
+ERROR: object subscript must be a string or number
+select jsonb '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb_path_query('{}', 'strict $[-2 to 3]');
+ERROR: jsonpath array accessor can only be applied to an array
+select jsonb '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
?column?
----------
@@ -289,7 +315,7 @@ select jsonb_path_query('{}', 'strict $.a', silent => true);
(0 rows)
select jsonb_path_query('1', 'strict $[1]');
-ERROR: jsonpath array accessor can only be applied to an array
+ERROR: jsonpath array accessor can only be applied to an array or object
select jsonb_path_query('1', 'strict $[*]');
ERROR: jsonpath wildcard array accessor can only be applied to an array
select jsonb_path_query('[]', 'strict $[1]');
@@ -404,6 +430,12 @@ select jsonb_path_query('1', 'lax $[*]');
1
(1 row)
+select jsonb_path_query('{}', 'lax $[0]');
+ jsonb_path_query
+------------------
+ {}
+(1 row)
+
select jsonb_path_query('[1]', 'lax $[0]');
jsonb_path_query
------------------
@@ -454,6 +486,12 @@ select jsonb_path_query('[1]', '$[last]');
1
(1 row)
+select jsonb_path_query('{}', '$[last]');
+ jsonb_path_query
+------------------
+ {}
+(1 row)
+
select jsonb_path_query('[1,2,3]', '$[last]');
jsonb_path_query
------------------
@@ -1510,18 +1548,16 @@ select jsonb_path_query('"NaN"', '$.double()');
(1 row)
select jsonb_path_query('"inf"', '$.double()');
-ERROR: jsonpath item method .double() can only be applied to a numeric value
-select jsonb_path_query('"-inf"', '$.double()');
-ERROR: jsonpath item method .double() can only be applied to a numeric value
-select jsonb_path_query('"inf"', '$.double()', silent => true);
jsonb_path_query
------------------
-(0 rows)
+ "Infinity"
+(1 row)
-select jsonb_path_query('"-inf"', '$.double()', silent => true);
+select jsonb_path_query('"-inf"', '$.double()');
jsonb_path_query
------------------
-(0 rows)
+ "-Infinity"
+(1 row)
select jsonb_path_query('{}', '$.abs()');
ERROR: jsonpath item method .abs() can only be applied to a numeric value
@@ -1622,6 +1658,584 @@ select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "
"abdacb"
(2 rows)
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "a\\b" flag "q")');
+ jsonb_path_query
+------------------
+ "a\\b"
+ "^a\\b$"
+(2 rows)
+
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "a\\b" flag "")');
+ jsonb_path_query
+------------------
+ "a\b"
+(1 row)
+
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "q")');
+ jsonb_path_query
+------------------
+ "^a\\b$"
+(1 row)
+
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "q")');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")');
+ jsonb_path_query
+------------------
+ "^a\\b$"
+(1 row)
+
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")');
+ jsonb_path_query
+------------------
+ "a\b"
+(1 row)
+
+select jsonb_path_query('null', '$.datetime()');
+ERROR: jsonpath item method .datetime() can only be applied to a string or number
+select jsonb_path_query('true', '$.datetime()');
+ERROR: jsonpath item method .datetime() can only be applied to a string or number
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR: jsonpath item method .datetime() can only be applied to a string or number
+select jsonb_path_query('{}', '$.datetime()');
+ERROR: jsonpath item method .datetime() can only be applied to a string or number
+select jsonb_path_query('""', '$.datetime()');
+ERROR: unrecognized datetime format
+HINT: use datetime template argument for explicit format specification
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+ERROR: datetime format is not dated and not timed
+select jsonb_path_query('"12:34"', '$.datetime("aaa", 1)');
+ERROR: datetime format is not dated and not timed
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 1)');
+ jsonb_path_query
+---------------------
+ "12:34:00+00:00:01"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)');
+ERROR: timezone argument of jsonpath item method .datetime() is out of integer range
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)');
+ERROR: timezone argument of jsonpath item method .datetime() is out of integer range
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483647)');
+ jsonb_path_query
+-------------------------
+ "12:34:00+596523:14:07"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483648)');
+ERROR: timezone argument of jsonpath item method .datetime() is out of integer range
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483647)');
+ jsonb_path_query
+-------------------------
+ "12:34:00-596523:14:07"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483648)');
+ERROR: timezone argument of jsonpath item method .datetime() is out of integer range
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+ERROR: invalid value "aa" for "HH24"
+DETAIL: Value must be an integer.
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb_path_query('0', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select jsonb_path_query('0', '$.datetime().type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('1490216035.5', '$.datetime()');
+ jsonb_path_query
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+ jsonb_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+ jsonb_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: missing time-zone in input string for type timestamptz
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:12"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+ jsonb_path_query
+--------------------------------
+ "2017-03-10T12:34:00-00:12:34"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+ERROR: invalid input syntax for type timestamptz: "UTC"
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", 12 * 60)');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:12"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", -(5 * 3600 + 12 * 60 + 34))');
+ jsonb_path_query
+--------------------------------
+ "2017-03-10T12:34:00-05:12:34"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)',
+ jsonb_build_object('tz', extract(timezone from now())));
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: missing time-zone in input string for type timetz
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+ jsonb_path_query
+------------------
+ "12:34:00+00:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: missing time-zone in input string for type timestamptz
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+10:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)',
+ jsonb_build_object('tz', extract(timezone from now())));
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+10:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: missing time-zone in input string for type timetz
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+ jsonb_path_query
+------------------
+ "12:34:00+10:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+ jsonb_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+ jsonb_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+(4 rows)
+
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+------------------
+ "2017-03-09"
+(1 row)
+
+-- time comparison
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:35:00"
+(1 row)
+
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:35:00"
+ "12:36:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:34:00"
+(1 row)
+
+-- timetz comparison
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+(3 rows)
+
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+(2 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:35:00"
+(1 row)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-11"
+(3 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10"
+(2 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+(3 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+(2 rows)
+
+set time zone default;
-- jsonpath operators
SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
jsonb_path_query
@@ -1635,6 +2249,12 @@ SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
------------------
(0 rows)
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '[$[*].a]');
+ jsonb_path_query
+------------------
+ [1, 2]
+(1 row)
+
SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
ERROR: JSON object does not contain key "a"
SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
@@ -1667,6 +2287,12 @@ SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].
[]
(1 row)
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '[$[*].a]');
+ jsonb_path_query_array
+------------------------
+ [[1, 2]]
+(1 row)
+
SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
ERROR: JSON object does not contain key "a"
SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a', silent => true);
@@ -1797,3 +2423,337 @@ SELECT jsonb_path_match('[{"a": 1}, {"a": 2}]', '$[*].a > 1');
t
(1 row)
+-- extension: path sequences
+select jsonb_path_query('[1,2,3,4,5]', '10, 20, $[*], 30');
+ jsonb_path_query
+------------------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select jsonb_path_query('[1,2,3,4,5]', 'lax 10, 20, $[*].a, 30');
+ jsonb_path_query
+------------------
+ 10
+ 20
+ 30
+(3 rows)
+
+select jsonb_path_query('[1,2,3,4,5]', 'strict 10, 20, $[*].a, 30');
+ERROR: jsonpath member accessor can only be applied to an object
+select jsonb_path_query('[1,2,3,4,5]', '-(10, 20, $[1 to 3], 30)');
+ jsonb_path_query
+------------------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select jsonb_path_query('[1,2,3,4,5]', 'lax (10, 20.5, $[1 to 3], "30").double()');
+ jsonb_path_query
+------------------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 5) ? (@ == 3)]');
+ jsonb_path_query
+------------------
+ 4
+(1 row)
+
+select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 3) ? (@ == 3)]');
+ERROR: jsonpath array subscript is not a single numeric value
+-- extension: array constructors
+select jsonb_path_query('[1, 2, 3]', '[]');
+ jsonb_path_query
+------------------
+ []
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5]');
+ jsonb_path_query
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5][*]');
+ jsonb_path_query
+------------------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select jsonb_path_query('[1, 2, 3]', '[(1, (2, $[*])), (4, 5)]');
+ jsonb_path_query
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]');
+ jsonb_path_query
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'strict [1, 2, $[*].a, 4, 5]');
+ERROR: jsonpath member accessor can only be applied to an object
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '[$[*][*] ? (@ > 3)]');
+ jsonb_path_query
+------------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select jsonb_path_query('[1, 2, 3]', '{}');
+ jsonb_path_query
+------------------
+ {}
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}');
+ jsonb_path_query
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}.*');
+ jsonb_path_query
+------------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}[*]');
+ jsonb_path_query
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": ($[*], 4, 5)}');
+ERROR: value in jsonpath object constructor must be a singleton
+select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}');
+ jsonb_path_query
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"a": 1}', '$["a"]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": 1}', 'strict $["b"]');
+ERROR: JSON object does not contain the specified key
+select jsonb_path_query('{"a": 1}', 'lax $["b"]');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": 1, "b": 2}', 'lax $["b", "c", "b", "a", 0 to 3]');
+ jsonb_path_query
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select jsonb_path_query('null', '{"a": 1}["a"]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('null', '{"a": 1}["b"]');
+ jsonb_path_query
+------------------
+(0 rows)
+
+-- extension: outer item reference (@N)
+select jsonb_path_query('[2,4,1,5,3]', '$[*] ? (!exists($[*] ? (@ < @1)))');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[2,4,1,5,3]', '$[*] ? (!exists($[*] ? (@ > @1)))');
+ jsonb_path_query
+------------------
+ 5
+(1 row)
+
+select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ < @1[1]) > 2)');
+ jsonb_path_query
+------------------
+ [2, 4, 1, 5, 3]
+(1 row)
+
+select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ < @1[1]) > 3)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ ? (@ ? (@ < @3[1]) > @2[0]) > @1[0]) > 2)');
+ jsonb_path_query
+------------------
+ [2, 4, 1, 5, 3]
+(1 row)
+
+select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ ? (@ ? (@ < @3[2]) > @2[0]) > @1[0]) > 2)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+-- extension: including subpaths into result
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[*].b)');
+ jsonb_path_query
+-----------------------------
+ {"a": [{"b": 1}, {"b": 2}]}
+(1 row)
+
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[*]).b');
+ jsonb_path_query
+------------------
+ {"a": [1, 2]}
+(1 row)
+
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.a.([*].b)');
+ jsonb_path_query
+----------------------
+ [{"b": 1}, {"b": 2}]
+(1 row)
+
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a)[*].b');
+ jsonb_path_query
+------------------
+ {"a": 1}
+ {"a": 2}
+(2 rows)
+
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.a[*].(b)');
+ jsonb_path_query
+------------------
+ {"b": 1}
+ {"b": 2}
+(2 rows)
+
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a)[*].(b)');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+ {"a": {"b": 2}}
+(2 rows)
+
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[0 to 1].b)');
+ jsonb_path_query
+-----------------------------
+ {"a": [{"b": 1}, {"b": 2}]}
+(1 row)
+
+-- extension: user-defined functions and item methods
+-- array_map(jsonpath_fcxt, jsonb) function created in create_function_1.sql
+-- array_map() item method
+select jsonb_path_query('1', 'strict $.array_map(x => x + 10)');
+ERROR: SQL/JSON array not found
+DETAIL: jsonpath .array_map() is applied to not an array
+select jsonb_path_query('1', 'lax $.array_map(x => x + 10)');
+ jsonb_path_query
+------------------
+ 11
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '$.array_map(x => x + 10)');
+ jsonb_path_query
+------------------
+ [11, 12, 13]
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '$.array_map(x => x + 10)[*]');
+ jsonb_path_query
+------------------
+ 11
+ 12
+ 13
+(3 rows)
+
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.array_map(a => a.array_map(x => x + 10))');
+ jsonb_path_query
+----------------------------------------
+ [[11, 12], [13, 14, 15], [], [16, 17]]
+(1 row)
+
+-- array_map() function
+select jsonb_path_query('1', 'strict array_map($, x => x + 10)');
+ jsonb_path_query
+------------------
+ 11
+(1 row)
+
+select jsonb_path_query('1', 'lax array_map($, x => x + 10)');
+ jsonb_path_query
+------------------
+ 11
+(1 row)
+
+select jsonb_path_query('[3, 4, 5]', 'array_map($[*], (x, i) => x + i * 10)');
+ jsonb_path_query
+------------------
+ 3
+ 14
+ 25
+(3 rows)
+
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'array_map($[*], x => [array_map(x[*], x => x + 10)])');
+ jsonb_path_query
+------------------
+ [11, 12]
+ [13, 14, 15]
+ []
+ [16, 17]
+(4 rows)
+
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
new file mode 100644
index 0000000000..034261e2c7
--- /dev/null
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -0,0 +1,2107 @@
+-- JSON_EXISTS
+SELECT JSON_EXISTS(NULL FORMAT JSONB, '$');
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS(NULL::text FORMAT JSONB, '$');
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS(NULL::bytea FORMAT JSONB, '$');
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS(NULL::json FORMAT JSONB, '$');
+WARNING: FORMAT JSON has no effect for json and jsonb types
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS(NULL::jsonb FORMAT JSONB, '$');
+WARNING: FORMAT JSON has no effect for json and jsonb types
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS(NULL::jsonb, '$');
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS('' FORMAT JSONB, '$');
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS('' FORMAT JSONB, '$' TRUE ON ERROR);
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS('' FORMAT JSONB, '$' FALSE ON ERROR);
+ json_exists
+-------------
+ f
+(1 row)
+
+SELECT JSON_EXISTS('' FORMAT JSONB, '$' UNKNOWN ON ERROR);
+ json_exists
+-------------
+
+(1 row)
+
+SELECT JSON_EXISTS('' FORMAT JSONB, '$' ERROR ON ERROR);
+ERROR: invalid input syntax for type json
+DETAIL: The input string ended unexpectedly.
+CONTEXT: JSON data, line 1:
+SELECT JSON_EXISTS(bytea '' FORMAT JSONB, '$' ERROR ON ERROR);
+ERROR: incorrect jsonb binary data format
+SELECT JSON_EXISTS(jsonb '[]', '$');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS('[]' FORMAT JSONB, '$');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_EXISTS(JSON_OBJECT(RETURNING bytea FORMAT JSONB) FORMAT 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)
+
+-- JSON_VALUE
+SELECT JSON_VALUE(NULL FORMAT JSONB, '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(NULL::text FORMAT JSONB, '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(NULL::bytea FORMAT JSONB, '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(NULL::json FORMAT JSONB, '$');
+WARNING: FORMAT JSON has no effect for json and jsonb types
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(NULL::jsonb FORMAT JSONB, '$');
+WARNING: FORMAT JSON has no effect for json and jsonb types
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE(NULL::jsonb, '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE('' FORMAT JSONB, '$');
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE('' FORMAT JSONB, '$' NULL ON ERROR);
+ json_value
+------------
+
+(1 row)
+
+SELECT JSON_VALUE('' FORMAT JSONB, '$' DEFAULT '"default value"' ON ERROR);
+ json_value
+-----------------
+ "default value"
+(1 row)
+
+SELECT JSON_VALUE('' FORMAT JSONB, '$' ERROR ON ERROR);
+ERROR: invalid input syntax for type json
+DETAIL: The input string ended unexpectedly.
+CONTEXT: JSON data, line 1:
+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 FORMAT JSONB, '$'),
+ JSON_QUERY(js FORMAT JSONB, '$' WITHOUT WRAPPER),
+ JSON_QUERY(js FORMAT JSONB, '$' WITH CONDITIONAL WRAPPER),
+ JSON_QUERY(js FORMAT JSONB, '$' WITH UNCONDITIONAL ARRAY WRAPPER),
+ JSON_QUERY(js FORMAT JSONB, '$' WITH ARRAY WRAPPER)
+FROM
+ (VALUES
+ ('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 FORMAT JSONB, 'strict $[*]') AS "unspec",
+ JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITHOUT WRAPPER) AS "without",
+ JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond",
+ JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond",
+ JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITH ARRAY WRAPPER) AS "with"
+FROM
+ (VALUES
+ ('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('"aaa"' FORMAT JSONB, '$' RETURNING text);
+ json_query
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text KEEP QUOTES);
+ json_query
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text KEEP QUOTES ON SCALAR STRING);
+ json_query
+------------
+ "aaa"
+(1 row)
+
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text OMIT QUOTES);
+ json_query
+------------
+ aaa
+(1 row)
+
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text OMIT QUOTES ON SCALAR STRING);
+ json_query
+------------
+ aaa
+(1 row)
+
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' 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('"aaa"' FORMAT JSONB, '$' 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('"aaa"' FORMAT JSONB, '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR);
+ json_query
+------------
+ \x616161
+(1 row)
+
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING bytea FORMAT JSONB OMIT QUOTES ERROR ON ERROR);
+ json_query
+------------
+ \x616161
+(1 row)
+
+-- QUOTES behavior should not be specified when WITH WRAPPER used:
+-- Should fail
+SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES);
+ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used
+LINE 1: SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES)...
+ ^
+SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER KEEP QUOTES);
+ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used
+LINE 1: SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER KEEP QUOTES)...
+ ^
+SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTES);
+ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used
+LINE 1: ...N_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTE...
+ ^
+SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES);
+ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used
+LINE 1: ...N_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTE...
+ ^
+-- Should succeed
+SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER OMIT QUOTES);
+ json_query
+------------
+ [1]
+(1 row)
+
+SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER KEEP QUOTES);
+ json_query
+------------
+ [1]
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]');
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' NULL ON EMPTY);
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' EMPTY ARRAY ON EMPTY);
+ json_query
+------------
+ []
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' EMPTY OBJECT ON EMPTY);
+ json_query
+------------
+ {}
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY);
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY NULL ON ERROR);
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR);
+ json_query
+------------
+ []
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR);
+ json_query
+------------
+ {}
+(1 row)
+
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY ERROR ON ERROR);
+ERROR: no SQL/JSON item
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON ERROR);
+ json_query
+------------
+
+(1 row)
+
+SELECT JSON_QUERY('[1,2]' FORMAT JSONB, '$[*]' 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]', '$' 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 text FORMAT JSONB);
+ 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 FORMAT JSONB);
+ json_query
+------------------------------------------------------------
+ \x02000040080000900800001020000000008001002000000000800200
+(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 bytea FORMAT JSONB EMPTY OBJECT ON ERROR);
+ json_query
+------------
+ \x00000020
+(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 FORMAT 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 FORMAT JSONB, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]')
+ CONSTRAINT test_jsonb_constraint5
+ CHECK (JSON_QUERY(js FORMAT JSONB, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a')
+);
+\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 NULL ON EMPTY NULL ON ERROR)
+Check constraints:
+ "test_jsonb_constraint1" CHECK (js IS JSON)
+ "test_jsonb_constraint2" CHECK (JSON_EXISTS(js FORMAT JSONB, '$."a"' PASSING i + 5 AS int, i::text AS txt, ARRAY[1, 2, 3] AS arr FALSE ON ERROR))
+ "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 FORMAT JSONB, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb)
+ "test_jsonb_constraint5" CHECK (JSON_QUERY(js FORMAT JSONB, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar)
+
+SELECT check_clause
+FROM information_schema.check_constraints
+WHERE constraint_name LIKE 'test_jsonb_constraint%';
+ check_clause
+---------------------------------------------------------------------------------------------------------------------------------------
+ ((js IS JSON))
+ (JSON_EXISTS(js FORMAT JSONB, '$."a"' PASSING (i + 5) AS int, (i)::text AS txt, ARRAY[1, 2, 3] AS arr FALSE ON ERROR))
+ ((JSON_VALUE((js)::jsonb, '$."a"' RETURNING integer DEFAULT (('12'::text || i))::integer ON EMPTY ERROR ON ERROR) > i))
+ ((JSON_QUERY(js FORMAT JSONB, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb))
+ ((JSON_QUERY(js FORMAT JSONB, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar))
+(5 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 NULL ON EMPTY NULL ON ERROR)
+(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;
+-- JSON_TABLE
+-- Should fail (JSON_TABLE can be used only in FROM clause)
+SELECT JSON_TABLE('[]', '$');
+ERROR: syntax error at or near "("
+LINE 1: SELECT JSON_TABLE('[]', '$');
+ ^
+-- Should fail (no columns)
+SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ());
+ERROR: syntax error at or near ")"
+LINE 1: SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ());
+ ^
+-- NULL => empty table
+SELECT * FROM JSON_TABLE(NULL FORMAT JSONB, '$' COLUMNS (foo int)) bar;
+ foo
+-----
+(0 rows)
+
+-- invalid json => empty table
+SELECT * FROM JSON_TABLE('' FORMAT JSONB, '$' COLUMNS (foo int)) bar;
+ foo
+-----
+(0 rows)
+
+-- invalid json => error
+SELECT * FROM JSON_TABLE('' FORMAT JSONB, '$' COLUMNS (foo int) ERROR ON ERROR) bar;
+ERROR: invalid input syntax for type json
+DETAIL: The input string ended unexpectedly.
+CONTEXT: JSON data, line 1:
+--
+SELECT * FROM JSON_TABLE('123' FORMAT JSONB, '$'
+ COLUMNS (item int PATH '$', foo int)) bar;
+ item | foo
+------+-----
+ 123 |
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '123', '$'
+ COLUMNS (item int PATH '$', foo int)) bar;
+ item | foo
+------+-----
+ 123 |
+(1 row)
+
+-- JSON_TABLE: basic functionality
+SELECT *
+FROM
+ (VALUES
+ ('1'),
+ ('[]'),
+ ('{}'),
+ ('[1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]'),
+ ('err')
+ ) vals(js)
+ LEFT OUTER JOIN
+-- JSON_TABLE is implicitly lateral
+ JSON_TABLE(
+ vals.js FORMAT JSONB, 'lax $[*]'
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY, -- allowed additional ordinality columns
+ "int" int PATH '$',
+ "text" text PATH '$',
+ "char(4)" char(4) PATH '$',
+ "bool" bool PATH '$',
+ "numeric" numeric PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc char(4) FORMAT JSON PATH '$',
+ jsv varchar(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSONB PATH '$',
+ aaa int, -- implicit path '$."aaa"',
+ aaa1 int PATH '$.aaa'
+ )
+ ) jt
+ ON true;
+ js | id | id2 | int | text | char(4) | bool | numeric | js | jb | jst | jsc | jsv | jsb | aaa | aaa1
+--------------------------------------------------------------------------------+----+-----+-----+---------+---------+------+---------+-----------+-----------+--------------+------+------+--------------+-----+------
+ 1 | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |
+ [] | | | | | | | | | | | | | | |
+ {} | 1 | 1 | | | | | | | | {} | {} | {} | {} | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 2 | 2 | 1 | 1.23 | 1.23 | | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 3 | 3 | 2 | 2 | 2 | | 2 | "2" | "2" | "2" | "2" | "2" | "2" | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 4 | 4 | | aaaaaaa | aaaa | | | "aaaaaaa" | "aaaaaaa" | "aaaaaaa" | "aaa | "aaa | "aaaaaaa" | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 5 | 5 | | | | | | | | null | null | null | null | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 6 | 6 | 0 | false | fals | f | | false | false | false | fals | fals | false | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 7 | 7 | 1 | true | true | t | | true | true | true | true | true | true | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 8 | 8 | | | | | | | | {"aaa": 123} | {"aa | {"aa | {"aaa": 123} | 123 | 123
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 9 | 9 | | [1,2] | [1,2 | | | "[1,2]" | "[1,2]" | "[1,2]" | "[1, | "[1, | "[1,2]" | |
+ [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 10 | 10 | | "str" | "str | | | "\"str\"" | "\"str\"" | "\"str\"" | "\"s | "\"s | "\"str\"" | |
+ err | | | | | | | | | | | | | | |
+(14 rows)
+
+-- JSON_TABLE: Test backward parsing
+CREATE VIEW jsonb_table_view AS
+SELECT * FROM
+ JSON_TABLE(
+ 'null' FORMAT JSONB, 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY, -- allowed additional ordinality columns
+ "int" int PATH '$',
+ "text" text PATH '$',
+ "char(4)" char(4) PATH '$',
+ "bool" bool PATH '$',
+ "numeric" numeric PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc char(4) FORMAT JSON PATH '$',
+ jsv varchar(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSONB PATH '$',
+ aaa int, -- implicit path '$."aaa"',
+ aaa1 int PATH '$.aaa',
+ NESTED PATH '$[1]' AS p1 COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' AS "p1 1" COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' AS p2 COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1" COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' AS p22 COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
+\sv jsonb_table_view
+CREATE OR REPLACE VIEW public.jsonb_table_view AS
+ SELECT "json_table".id,
+ "json_table".id2,
+ "json_table"."int",
+ "json_table".text,
+ "json_table"."char(4)",
+ "json_table".bool,
+ "json_table"."numeric",
+ "json_table".js,
+ "json_table".jb,
+ "json_table".jst,
+ "json_table".jsc,
+ "json_table".jsv,
+ "json_table".jsb,
+ "json_table".aaa,
+ "json_table".aaa1,
+ "json_table".a1,
+ "json_table".b1,
+ "json_table".a11,
+ "json_table".a21,
+ "json_table".a22
+ FROM JSON_TABLE(
+ 'null'::text FORMAT JSONB, '$[*]' AS json_table_path_1
+ PASSING
+ 1 + 2 AS a,
+ '"foo"'::json AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY,
+ "int" integer PATH '$',
+ text text PATH '$',
+ "char(4)" character(4) PATH '$',
+ bool boolean PATH '$',
+ "numeric" numeric PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc character(4) FORMAT JSON PATH '$',
+ jsv character varying(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSON PATH '$',
+ aaa integer PATH '$."aaa"',
+ aaa1 integer PATH '$."aaa"',
+ NESTED PATH '$[1]' AS p1
+ COLUMNS (
+ a1 integer PATH '$."a1"',
+ b1 text PATH '$."b1"',
+ NESTED PATH '$[*]' AS "p1 1"
+ COLUMNS (
+ a11 text PATH '$."a11"'
+ )
+ ),
+ NESTED PATH '$[2]' AS p2
+ COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1"
+ COLUMNS (
+ a21 text PATH '$."a21"'
+ ),
+ NESTED PATH '$[*]' AS p22
+ COLUMNS (
+ a22 text PATH '$."a22"'
+ )
+ )
+ )
+ PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22))))
+ )
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Table Function Scan on "json_table"
+ Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".aaa, "json_table".aaa1, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
+ Table Function Call: JSON_TABLE('null'::text FORMAT JSONB, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb FORMAT JSON PATH '$', aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))))
+(3 rows)
+
+-- JSON_TABLE: ON EMPTY/ON ERROR behavior
+SELECT *
+FROM
+ (VALUES ('1'), ('err'), ('"err"')) vals(js),
+ JSON_TABLE(vals.js FORMAT JSONB, '$' COLUMNS (a int PATH '$')) jt;
+ js | a
+-------+---
+ 1 | 1
+ "err" |
+(2 rows)
+
+SELECT *
+FROM
+ (VALUES ('1'), ('err'), ('"err"')) vals(js)
+ LEFT OUTER JOIN
+ JSON_TABLE(vals.js FORMAT JSONB, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt
+ ON true;
+ERROR: invalid input syntax for type json
+DETAIL: Token "err" is invalid.
+CONTEXT: JSON data, line 1: err
+SELECT *
+FROM
+ (VALUES ('1'), ('err'), ('"err"')) vals(js)
+ LEFT OUTER JOIN
+ JSON_TABLE(vals.js FORMAT JSONB, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
+ ON true;
+ERROR: invalid input syntax for type integer: "err"
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
+ a
+---
+
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: jsonpath member accessor can only be applied to an object
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: no SQL/JSON item
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 1
+(1 row)
+
+-- JSON_TABLE: nested paths and plans
+-- Should fail (JSON_TABLE columns shall contain explicit AS path
+-- specifications if explicit PLAN clause is used)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' -- AS required here
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 2: jsonb '[]', '$'
+ ^
+DETAIL: JSON_TABLE columns shall contain explicit AS pathname specification if explicit PLAN clause is used
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS ( -- AS required here
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 4: NESTED PATH '$' COLUMNS (
+ ^
+DETAIL: JSON_TABLE columns shall contain explicit AS pathname specification if explicit PLAN clause is used
+-- Should fail (column names anf path names shall be distinct)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS a
+ COLUMNS (
+ a int
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column name: a
+HINT: JSON_TABLE path names and column names shall be distinct from one another
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS a
+ COLUMNS (
+ b int,
+ NESTED PATH '$' AS a
+ COLUMNS (
+ c int
+ )
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column name: a
+HINT: JSON_TABLE path names and column names shall be distinct from one another
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$'
+ COLUMNS (
+ b int,
+ NESTED PATH '$' AS b
+ COLUMNS (
+ c int
+ )
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column name: b
+HINT: JSON_TABLE path names and column names shall be distinct from one another
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$'
+ COLUMNS (
+ NESTED PATH '$' AS a
+ COLUMNS (
+ b int
+ ),
+ NESTED PATH '$'
+ COLUMNS (
+ NESTED PATH '$' AS a
+ COLUMNS (
+ c int
+ )
+ )
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column name: a
+HINT: JSON_TABLE path names and column names shall be distinct from one another
+-- JSON_TABLE: plan validation
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p1)
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 12: PLAN (p1)
+ ^
+DETAIL: path name mismatch: expected p0 but p1 is given
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0)
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: plan node for nested path p1 was not found in plan
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER p3)
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: plan node for nested path p1 was not found in plan
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 UNION p1 UNION p11)
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 12: PLAN (p0 UNION p1 UNION p11)
+ ^
+DETAIL: expected INNER or OUTER JSON_TABLE plan node
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p13))
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 8: NESTED PATH '$' AS p2 COLUMNS (
+ ^
+DETAIL: plan node for nested path p2 was not found in plan
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 5: NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ ^
+DETAIL: plan node for nested path p11 was not found in plan
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 12: PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+ ^
+DETAIL: plan node contains some extra or duplicate sibling nodes
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER p11) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 6: NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ^
+DETAIL: plan node for nested path p12 was not found in plan
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 9: NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ ^
+DETAIL: plan node for nested path p21 was not found in plan
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)))
+) jt;
+ bar | foo | baz
+-----+-----+-----
+(0 rows)
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' -- without root path name
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 2: jsonb 'null', 'strict $[*]'
+ ^
+DETAIL: JSON_TABLE columns shall contain explicit AS pathname specification if explicit PLAN clause is used
+-- JSON_TABLE: plan execution
+CREATE TEMP TABLE jsonb_table_test (js text);
+INSERT INTO jsonb_table_test
+VALUES (
+ '[
+ {"a": 1, "b": [], "c": []},
+ {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]},
+ {"a": 3, "b": [1, 2], "c": []},
+ {"x": "4", "b": [1, 2], "c": 123}
+ ]'
+);
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-- default plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-- specific plan (p outer (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb union pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-- specific plan (p outer (pc union pb))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pc union pb))
+ ) jt;
+ n | a | c | b
+---+----+----+---
+ 1 | 1 | |
+ 2 | 2 | 10 |
+ 2 | 2 | |
+ 2 | 2 | 20 |
+ 2 | 2 | | 1
+ 2 | 2 | | 2
+ 2 | 2 | | 3
+ 3 | 3 | | 1
+ 3 | 3 | | 2
+ 4 | -1 | | 1
+ 4 | -1 | | 2
+(11 rows)
+
+-- default plan (inner, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(10 rows)
+
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb union pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(10 rows)
+
+-- default plan (inner, cross)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (cross, inner)
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+(9 rows)
+
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb cross pc))
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+(9 rows)
+
+-- default plan (outer, cross)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb cross pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+select
+ jt.*, b1 + 100 as b
+from
+ json_table (jsonb
+ '[
+ {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]},
+ {"a": 2, "b": [10, 20], "c": [1, null, 2]},
+ {"x": "3", "b": [11, 22, 33, 44]}
+ ]',
+ '$[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on error,
+ nested path 'strict $.b[*]' as pb columns (
+ b text format json path '$',
+ nested path 'strict $[*]' as pb1 columns (
+ b1 int path '$'
+ )
+ ),
+ nested path 'strict $.c[*]' as pc columns (
+ c text format json path '$',
+ nested path 'strict $[*]' as pc1 columns (
+ c1 int path '$'
+ )
+ )
+ )
+ --plan default(outer, cross)
+ plan(p outer ((pb inner pb1) cross (pc outer pc1)))
+ ) jt;
+ n | a | b | b1 | c | c1 | b
+---+---+--------------+-----+------+----+-----
+ 1 | 1 | [1, 10] | 1 | 1 | | 101
+ 1 | 1 | [1, 10] | 1 | null | | 101
+ 1 | 1 | [1, 10] | 1 | 2 | | 101
+ 1 | 1 | [1, 10] | 10 | 1 | | 110
+ 1 | 1 | [1, 10] | 10 | null | | 110
+ 1 | 1 | [1, 10] | 10 | 2 | | 110
+ 1 | 1 | [2] | 2 | 1 | | 102
+ 1 | 1 | [2] | 2 | null | | 102
+ 1 | 1 | [2] | 2 | 2 | | 102
+ 1 | 1 | [3, 30, 300] | 3 | 1 | | 103
+ 1 | 1 | [3, 30, 300] | 3 | null | | 103
+ 1 | 1 | [3, 30, 300] | 3 | 2 | | 103
+ 1 | 1 | [3, 30, 300] | 30 | 1 | | 130
+ 1 | 1 | [3, 30, 300] | 30 | null | | 130
+ 1 | 1 | [3, 30, 300] | 30 | 2 | | 130
+ 1 | 1 | [3, 30, 300] | 300 | 1 | | 400
+ 1 | 1 | [3, 30, 300] | 300 | null | | 400
+ 1 | 1 | [3, 30, 300] | 300 | 2 | | 400
+ 2 | 2 | | | | |
+ 3 | | | | | |
+(20 rows)
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(jsonb
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >= $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+ x | y | y | z
+---+---+--------------+---
+ 2 | 1 | [1, 2, 3] | 1
+ 2 | 1 | [1, 2, 3] | 2
+ 2 | 1 | [1, 2, 3] | 3
+ 3 | 1 | [1, 2, 3] | 1
+ 3 | 1 | [1, 2, 3] | 2
+ 3 | 1 | [1, 2, 3] | 3
+ 3 | 1 | [2, 3, 4, 5] | 2
+ 3 | 1 | [2, 3, 4, 5] | 3
+ 3 | 1 | [2, 3, 4, 5] | 4
+ 3 | 1 | [2, 3, 4, 5] | 5
+ 4 | 1 | [1, 2, 3] | 1
+ 4 | 1 | [1, 2, 3] | 2
+ 4 | 1 | [1, 2, 3] | 3
+ 4 | 1 | [2, 3, 4, 5] | 2
+ 4 | 1 | [2, 3, 4, 5] | 3
+ 4 | 1 | [2, 3, 4, 5] | 4
+ 4 | 1 | [2, 3, 4, 5] | 5
+ 4 | 1 | [3, 4, 5, 6] | 3
+ 4 | 1 | [3, 4, 5, 6] | 4
+ 4 | 1 | [3, 4, 5, 6] | 5
+ 4 | 1 | [3, 4, 5, 6] | 6
+ 2 | 2 | [1, 2, 3] | 2
+ 2 | 2 | [1, 2, 3] | 3
+ 3 | 2 | [1, 2, 3] | 2
+ 3 | 2 | [1, 2, 3] | 3
+ 3 | 2 | [2, 3, 4, 5] | 2
+ 3 | 2 | [2, 3, 4, 5] | 3
+ 3 | 2 | [2, 3, 4, 5] | 4
+ 3 | 2 | [2, 3, 4, 5] | 5
+ 4 | 2 | [1, 2, 3] | 2
+ 4 | 2 | [1, 2, 3] | 3
+ 4 | 2 | [2, 3, 4, 5] | 2
+ 4 | 2 | [2, 3, 4, 5] | 3
+ 4 | 2 | [2, 3, 4, 5] | 4
+ 4 | 2 | [2, 3, 4, 5] | 5
+ 4 | 2 | [3, 4, 5, 6] | 3
+ 4 | 2 | [3, 4, 5, 6] | 4
+ 4 | 2 | [3, 4, 5, 6] | 5
+ 4 | 2 | [3, 4, 5, 6] | 6
+ 2 | 3 | [1, 2, 3] | 3
+ 3 | 3 | [1, 2, 3] | 3
+ 3 | 3 | [2, 3, 4, 5] | 3
+ 3 | 3 | [2, 3, 4, 5] | 4
+ 3 | 3 | [2, 3, 4, 5] | 5
+ 4 | 3 | [1, 2, 3] | 3
+ 4 | 3 | [2, 3, 4, 5] | 3
+ 4 | 3 | [2, 3, 4, 5] | 4
+ 4 | 3 | [2, 3, 4, 5] | 5
+ 4 | 3 | [3, 4, 5, 6] | 3
+ 4 | 3 | [3, 4, 5, 6] | 4
+ 4 | 3 | [3, 4, 5, 6] | 5
+ 4 | 3 | [3, 4, 5, 6] | 6
+(52 rows)
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ jsonb '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+ERROR: could not find jsonpath variable "x"
+-- Extension: non-constant JSON path
+SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a');
+ json_exists
+-------------
+ t
+(1 row)
+
+SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a');
+ json_value
+------------
+ 123
+(1 row)
+
+SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY);
+ json_value
+------------
+ foo
+(1 row)
+
+SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a');
+ json_query
+------------
+ 123
+(1 row)
+
+SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
+ json_query
+------------
+ [123]
+(1 row)
+
+-- Should fail (invalid path)
+SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error');
+ERROR: syntax error, unexpected IDENT_P, expecting $end at or near "error" of jsonpath input
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
+ERROR: only string constants supported in JSON_TABLE path specification
+LINE 1: SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || '...
+ ^
+-- Test parallel JSON_VALUE()
+CREATE TABLE test_parallel_jsonb_value AS
+SELECT i::text::jsonb AS js
+FROM generate_series(1, 1000000) i;
+-- Should be non-parallel due to subtransactions
+EXPLAIN (COSTS OFF)
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
+ QUERY PLAN
+---------------------------------------------
+ Aggregate
+ -> Seq Scan on test_parallel_jsonb_value
+(2 rows)
+
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
+ sum
+--------------
+ 500000500000
+(1 row)
+
+-- Should be parallel
+EXPLAIN (COSTS OFF)
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value;
+ QUERY PLAN
+------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on test_parallel_jsonb_value
+(5 rows)
+
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value;
+ sum
+--------------
+ 500000500000
+(1 row)
+
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index 0f9cd17e2e..851d2f52cc 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -190,6 +190,10 @@ select '''\x50\u0067\u{53}\u{051}\u{00004C}'''::jsonpath;
(1 row)
select '$.foo\x50\u0067\u{53}\u{051}\u{00004C}\t\"bar'::jsonpath;
+ERROR: syntax error, unexpected $undefined, expecting $end at or near "\" of jsonpath input
+LINE 1: select '$.foo\x50\u0067\u{53}\u{051}\u{00004C}\t\"bar'::json...
+ ^
+select '$."foo\x50\u0067\u{53}\u{051}\u{00004C}\t\"bar"'::jsonpath;
jsonpath
---------------------
$."fooPgSQL\t\"bar"
@@ -401,6 +405,24 @@ select '$.keyvalue().key'::jsonpath;
$.keyvalue()."key"
(1 row)
+select '$.datetime()'::jsonpath;
+ jsonpath
+--------------
+ $.datetime()
+(1 row)
+
+select '$.datetime("datetime template")'::jsonpath;
+ jsonpath
+---------------------------------
+ $.datetime("datetime template")
+(1 row)
+
+select '$.datetime("datetime template", "default timezone")'::jsonpath;
+ jsonpath
+-----------------------------------------------------
+ $.datetime("datetime template", "default timezone")
+(1 row)
+
select '$ ? (@ starts with "abc")'::jsonpath;
jsonpath
-------------------------
@@ -453,6 +475,24 @@ select '$ ? (@ like_regex "pattern" flag "xsms")'::jsonpath;
$?(@ like_regex "pattern" flag "sx")
(1 row)
+select '$ ? (@ like_regex "pattern" flag "q")'::jsonpath;
+ jsonpath
+-------------------------------------
+ $?(@ like_regex "pattern" flag "q")
+(1 row)
+
+select '$ ? (@ like_regex "pattern" flag "iq")'::jsonpath;
+ jsonpath
+--------------------------------------
+ $?(@ like_regex "pattern" flag "iq")
+(1 row)
+
+select '$ ? (@ like_regex "pattern" flag "smixq")'::jsonpath;
+ jsonpath
+----------------------------------------
+ $?(@ like_regex "pattern" flag "imxq")
+(1 row)
+
select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath;
ERROR: invalid input syntax for type jsonpath
LINE 1: select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath;
@@ -528,6 +568,72 @@ select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
(($ + 1)."a" + 2."b"?(@ > 1 || exists (@."c")))
(1 row)
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+ jsonpath
+------------------------
+ 1, 2 + 3, $."a"[*] + 5
+(1 row)
+
+select '(1, 2, $.a)'::jsonpath;
+ jsonpath
+-------------
+ 1, 2, $."a"
+(1 row)
+
+select '(1, 2, $.a).a[*]'::jsonpath;
+ jsonpath
+----------------------
+ (1, 2, $."a")."a"[*]
+(1 row)
+
+select '(1, 2, $.a) == 5'::jsonpath;
+ jsonpath
+----------------------
+ ((1, 2, $."a") == 5)
+(1 row)
+
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+ jsonpath
+----------------------------
+ $[(1, 2, $."a") to (3, 4)]
+(1 row)
+
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+ jsonpath
+-----------------------------
+ $[(1, (2, $."a")),3,(4, 5)]
+(1 row)
+
+select '[]'::jsonpath;
+ jsonpath
+----------
+ []
+(1 row)
+
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+ jsonpath
+------------------------------------------
+ [[1, 2], ([(3, 4, 5), 6], []), $."a"[*]]
+(1 row)
+
+select '{}'::jsonpath;
+ jsonpath
+----------
+ {}
+(1 row)
+
+select '{a: 1 + 2}'::jsonpath;
+ jsonpath
+--------------
+ {"a": 1 + 2}
+(1 row)
+
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+ jsonpath
+-----------------------------------------------------------------------
+ {"a": 1 + 2, "b": (1, 2), "c": [$[*], 4, 5], "d": {"e e e": "f f f"}}
+(1 row)
+
select '$ ? (@.a < 1)'::jsonpath;
jsonpath
---------------
@@ -799,9 +905,17 @@ select '0'::jsonpath;
(1 row)
select '00'::jsonpath;
-ERROR: syntax error, unexpected IDENT_P at end of jsonpath input
+ERROR: syntax error, unexpected INT_P, expecting $end at or near "0" of jsonpath input
LINE 1: select '00'::jsonpath;
^
+select '$.00'::jsonpath;
+ERROR: syntax error, unexpected INT_P at or near "0" of jsonpath input
+LINE 1: select '$.00'::jsonpath;
+ ^
+select '$.0a'::jsonpath;
+ERROR: syntax error, unexpected INT_P at or near "0" of jsonpath input
+LINE 1: select '$.0a'::jsonpath;
+ ^
select '0.0'::jsonpath;
jsonpath
----------
@@ -940,3 +1054,370 @@ select '(1.).e3'::jsonpath;
ERROR: syntax error, unexpected ')' at or near ")" of jsonpath input
LINE 1: select '(1.).e3'::jsonpath;
^
+select '@1'::jsonpath;
+ERROR: invalid outer item reference in jsonpath @
+LINE 1: select '@1'::jsonpath;
+ ^
+select '@-1'::jsonpath;
+ERROR: @ is not allowed in root expressions
+LINE 1: select '@-1'::jsonpath;
+ ^
+select '$ ? (@0 > 1)'::jsonpath;
+ jsonpath
+-----------
+ $?(@ > 1)
+(1 row)
+
+select '$ ? (@1 > 1)'::jsonpath;
+ERROR: invalid outer item reference in jsonpath @
+LINE 1: select '$ ? (@1 > 1)'::jsonpath;
+ ^
+select '$.a ? (@.b ? (@1 > @) > 5)'::jsonpath;
+ jsonpath
+----------------------------
+ $."a"?(@."b"?(@1 > @) > 5)
+(1 row)
+
+select '$.a ? (@.b ? (@2 > @) > 5)'::jsonpath;
+ERROR: invalid outer item reference in jsonpath @
+LINE 1: select '$.a ? (@.b ? (@2 > @) > 5)'::jsonpath;
+ ^
+-- jsonpath combination operators
+select jsonpath '$.a' == jsonpath '$[*] + 1';
+ ?column?
+---------------------
+ ($."a" == $[*] + 1)
+(1 row)
+
+-- should fail
+select jsonpath '$.a' == jsonpath '$.b == 1';
+ ?column?
+-------------------------
+ ($."a" == ($."b" == 1))
+(1 row)
+
+--select jsonpath '$.a' != jsonpath '$[*] + 1';
+select jsonpath '$.a' > jsonpath '$[*] + 1';
+ ?column?
+--------------------
+ ($."a" > $[*] + 1)
+(1 row)
+
+select jsonpath '$.a' < jsonpath '$[*] + 1';
+ ?column?
+--------------------
+ ($."a" < $[*] + 1)
+(1 row)
+
+select jsonpath '$.a' >= jsonpath '$[*] + 1';
+ ?column?
+---------------------
+ ($."a" >= $[*] + 1)
+(1 row)
+
+select jsonpath '$.a' <= jsonpath '$[*] + 1';
+ ?column?
+---------------------
+ ($."a" <= $[*] + 1)
+(1 row)
+
+select jsonpath '$.a' + jsonpath '$[*] + 1';
+ ?column?
+----------------------
+ ($."a" + ($[*] + 1))
+(1 row)
+
+select jsonpath '$.a' - jsonpath '$[*] + 1';
+ ?column?
+----------------------
+ ($."a" - ($[*] + 1))
+(1 row)
+
+select jsonpath '$.a' * jsonpath '$[*] + 1';
+ ?column?
+----------------------
+ ($."a" * ($[*] + 1))
+(1 row)
+
+select jsonpath '$.a' / jsonpath '$[*] + 1';
+ ?column?
+----------------------
+ ($."a" / ($[*] + 1))
+(1 row)
+
+select jsonpath '$.a' % jsonpath '$[*] + 1';
+ ?column?
+----------------------
+ ($."a" % ($[*] + 1))
+(1 row)
+
+select jsonpath '$.a' == jsonb '"aaa"';
+ ?column?
+------------------
+ ($."a" == "aaa")
+(1 row)
+
+--select jsonpath '$.a' != jsonb '1';
+select jsonpath '$.a' > jsonb '12.34';
+ ?column?
+-----------------
+ ($."a" > 12.34)
+(1 row)
+
+select jsonpath '$.a' < jsonb '"aaa"';
+ ?column?
+-----------------
+ ($."a" < "aaa")
+(1 row)
+
+select jsonpath '$.a' >= jsonb 'true';
+ ?column?
+-----------------
+ ($."a" >= true)
+(1 row)
+
+select jsonpath '$.a' <= jsonb 'false';
+ ?column?
+------------------
+ ($."a" <= false)
+(1 row)
+
+select jsonpath '$.a' + jsonb 'null';
+ ?column?
+----------------
+ ($."a" + null)
+(1 row)
+
+select jsonpath '$.a' - jsonb '12.3';
+ ?column?
+----------------
+ ($."a" - 12.3)
+(1 row)
+
+select jsonpath '$.a' * jsonb '5';
+ ?column?
+-------------
+ ($."a" * 5)
+(1 row)
+
+select jsonpath '$.a' / jsonb '0';
+ ?column?
+-------------
+ ($."a" / 0)
+(1 row)
+
+select jsonpath '$.a' % jsonb '"1.23"';
+ ?column?
+------------------
+ ($."a" % "1.23")
+(1 row)
+
+select jsonpath '$.a' == jsonb '[]';
+ ?column?
+---------------
+ ($."a" == [])
+(1 row)
+
+select jsonpath '$.a' >= jsonb '[1, "2", true, null, [], {"a": [1], "b": 3}]';
+ ?column?
+---------------------------------------------------------
+ ($."a" >= [1, "2", true, null, [], {"a": [1], "b": 3}])
+(1 row)
+
+select jsonpath '$.a' + jsonb '{}';
+ ?column?
+--------------
+ ($."a" + {})
+(1 row)
+
+select jsonpath '$.a' / jsonb '{"a": 1, "b": [1, {}], "c": {}, "d": {"e": true, "f": {"g": "abc"}}}';
+ ?column?
+--------------------------------------------------------------------------------
+ ($."a" / {"a": 1, "b": [1, {}], "c": {}, "d": {"e": true, "f": {"g": "abc"}}})
+(1 row)
+
+select jsonpath '$' -> 'a';
+ ?column?
+----------
+ $."a"
+(1 row)
+
+select jsonpath '$' -> 1;
+ ?column?
+----------
+ $[1]
+(1 row)
+
+select jsonpath '$' -> 'a' -> 1;
+ ?column?
+----------
+ $."a"[1]
+(1 row)
+
+select jsonpath '$.a' ? jsonpath '$.x ? (@.y ? (@ > 3 + @1.b + $) == $) > $.z';
+ ?column?
+-------------------------------------------------------------
+ $."a"?(@."x"?(@."y"?(@ > (3 + @1."b") + @2) == @1) > @."z")
+(1 row)
+
+select jsonpath '$.a.b[($[*]?(@ > @0).c + 1.23).**{2 to 5}] ? ({a: @, b: [$.x, [], @ % 5]}.b[2] > 3)' ?
+ jsonpath '$.**[$.size() + 3] ? (@ + $ ? (@ > @1 ? ($1 + $2 * @ - $ != 5) / $) < 10) > true';
+ ?column?
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ $."a"."b"[$[*]?(@ > @)."c" + 1.23.**{2 to 5}]?({"a": @, "b": [$."x", [], @ % 5]}."b"[2] > 3)?(@.**[@.size() + 3]?(@ + @1?(@ > @1?(($"1" + $"2" * @) - @3 != 5) / @2) < 10) > true)
+(1 row)
+
+select jsonpath '$.a + $a' @ jsonb '"aaa"';
+ ?column?
+----------------
+ ($."a" + $"a")
+(1 row)
+
+select jsonpath '$.a + $a' @ jsonb '{"b": "abc"}';
+ ?column?
+----------------
+ ($."a" + $"a")
+(1 row)
+
+select jsonpath '$.a + $a' @ jsonb '{"a": "abc"}';
+ ?column?
+-----------------
+ ($."a" + "abc")
+(1 row)
+
+select jsonpath '$.a + $a.double()' @ jsonb '{"a": "abc"}';
+ ?column?
+--------------------------
+ ($."a" + "abc".double())
+(1 row)
+
+select jsonpath '$.a + $a.x.double()' @ jsonb '{"a": {"x": -12.34}}';
+ ?column?
+--------------------------------------
+ ($."a" + {"x": -12.34}."x".double())
+(1 row)
+
+select jsonpath '$[*] ? (@ > $min && @ <= $max)' @ jsonb '{"min": -1.23, "max": 5.0}';
+ ?column?
+------------------------------
+ $[*]?(@ > -1.23 && @ <= 5.0)
+(1 row)
+
+select jsonpath '$[*] ? (@ > $min && @ <= $max)' @ jsonb '{"min": -1.23}' @ jsonb '{"max": 5.0}';
+ ?column?
+------------------------------
+ $[*]?(@ > -1.23 && @ <= 5.0)
+(1 row)
+
+-- extension: user-defined item methods and functions with lambda expressions
+select jsonpath 'foo()';
+ jsonpath
+----------
+ "foo"()
+(1 row)
+
+select jsonpath '$.foo()';
+ jsonpath
+-----------
+ $."foo"()
+(1 row)
+
+select jsonpath 'foo($[*])';
+ jsonpath
+-------------
+ "foo"($[*])
+(1 row)
+
+select jsonpath '$.foo("bar")';
+ jsonpath
+----------------
+ $."foo"("bar")
+(1 row)
+
+select jsonpath 'foo($[*], "bar")';
+ jsonpath
+--------------------
+ "foo"($[*], "bar")
+(1 row)
+
+select jsonpath '$.foo("bar", 123 + 456, "baz".type())';
+ jsonpath
+-----------------------------------------
+ $."foo"("bar", 123 + 456, "baz".type())
+(1 row)
+
+select jsonpath 'foo($[*], "bar", 123 + 456, "baz".type())';
+ jsonpath
+---------------------------------------------
+ "foo"($[*], "bar", 123 + 456, "baz".type())
+(1 row)
+
+select jsonpath '$.foo(() => 1)';
+ jsonpath
+------------------
+ $."foo"(() => 1)
+(1 row)
+
+select jsonpath 'foo($[*], () => 1)';
+ jsonpath
+----------------------
+ "foo"($[*], () => 1)
+(1 row)
+
+select jsonpath '$.foo((x) => x + 5)';
+ jsonpath
+-----------------------
+ $."foo"((x) => x + 5)
+(1 row)
+
+select jsonpath 'foo($[*], (x) => x + 5)';
+ jsonpath
+---------------------------
+ "foo"($[*], (x) => x + 5)
+(1 row)
+
+select jsonpath '$.foo(x => x + 5)';
+ jsonpath
+-----------------------
+ $."foo"((x) => x + 5)
+(1 row)
+
+select jsonpath 'foo($[*], x => x + 5)';
+ jsonpath
+---------------------------
+ "foo"($[*], (x) => x + 5)
+(1 row)
+
+select jsonpath '$.foo((x, y) => x + 5 * y)';
+ jsonpath
+------------------------------
+ $."foo"((x, y) => x + 5 * y)
+(1 row)
+
+select jsonpath 'foo($[*], (x, y) => x + 5 * y)';
+ jsonpath
+----------------------------------
+ "foo"($[*], (x, y) => x + 5 * y)
+(1 row)
+
+select jsonpath '$.foo((x, y) => x + 5 * y, z => z.type(), () => $.bar(x => x + 1)).baz()';
+ jsonpath
+------------------------------------------------------------------------------------
+ $."foo"((x, y) => x + 5 * y, (z) => z.type(), () => $."bar"((x) => x + 1))."baz"()
+(1 row)
+
+select jsonpath 'foo($[*], (x, y) => x + 5 * y, z => z.type(), () => $.bar(x => x + 1)).baz()';
+ jsonpath
+----------------------------------------------------------------------------------------
+ "foo"($[*], (x, y) => x + 5 * y, (z) => z.type(), () => $."bar"((x) => x + 1))."baz"()
+(1 row)
+
+-- should fail
+select jsonpath '$.foo((x, y.a) => x + 5)';
+ERROR: lambda arguments must be identifiers at or near ")" of jsonpath input
+LINE 1: select jsonpath '$.foo((x, y.a) => x + 5)';
+ ^
+select jsonpath '$.foo((x, y + 1, z) => x + y)';
+ERROR: lambda arguments must be identifiers at or near ")" of jsonpath input
+LINE 1: select jsonpath '$.foo((x, y + 1, z) => x + y)';
+ ^
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 85af36ee5b..aea60b53ad 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -210,11 +210,12 @@ WHERE p1.oid != p2.oid AND
ORDER BY 1, 2;
proargtypes | proargtypes
-------------+-------------
+ 25 | 114
25 | 1042
25 | 1043
1114 | 1184
1560 | 1562
-(4 rows)
+(5 rows)
SELECT DISTINCT p1.proargtypes[1], p2.proargtypes[1]
FROM pg_proc AS p1, pg_proc AS p2
@@ -878,6 +879,9 @@ WHERE c.castfunc = p.oid AND
-- texttoxml(), which does an XML syntax check.
-- As of 9.1, this finds the cast from pg_node_tree to text, which we
-- intentionally do not provide a reverse pathway for.
+-- As of 10.0, this finds the cast from jsonb to bytea, because those are
+-- binary-compatible while the reverse goes through jsonb_from_bytea(),
+-- which does a jsonb structure validation.
SELECT castsource::regtype, casttarget::regtype, castfunc, castcontext
FROM pg_cast c
WHERE c.castmethod = 'b' AND
@@ -897,7 +901,8 @@ WHERE c.castmethod = 'b' AND
xml | text | 0 | a
xml | character varying | 0 | a
xml | character | 0 | a
-(10 rows)
+ jsonb | bytea | 0 | e
+(11 rows)
-- **************** pg_conversion ****************
-- Look for illegal values in pg_conversion fields.
@@ -1405,8 +1410,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..bf571371fe
--- /dev/null
+++ b/src/test/regress/expected/sqljson.out
@@ -0,0 +1,1084 @@
+-- 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 json FORMAT JSONB);
+ 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 jsonb FORMAT JSONB);
+ 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 text FORMAT JSONB);
+ json_object
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING text FORMAT JSONB ENCODING UTF8);
+ERROR: syntax error at or near "ENCODING"
+LINE 1: SELECT JSON_OBJECT(RETURNING text FORMAT JSONB ENCODING UTF8...
+ ^
+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(RETURNING bytea FORMAT JSONB);
+ json_object
+-------------
+ \x00000020
+(1 row)
+
+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
+ 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
+ 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 3 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", "1.23" : 123, "c" : [ 1,true,{ } ], "d" : {"x": 123.45}}
+(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 text) FORMAT JSONB);
+ 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': JSON_OBJECT('b': 1 RETURNING bytea FORMAT JSONB));
+ json_object
+---------------------------------------------------------------
+ {"a" : "\\x01000020010000800b000010620000002000000000800100"}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea FORMAT JSONB) FORMAT JSONB);
+ json_object
+------------------
+ {"a" : {"b": 1}}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea FORMAT JSON) FORMAT JSONB);
+ERROR: incorrect jsonb binary data format
+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 key "1"
+SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+ERROR: duplicate JSON key "1"
+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 key "1"
+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 json FORMAT JSONB);
+ 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 jsonb FORMAT JSONB);
+ 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 text FORMAT JSONB);
+ json_array
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING text FORMAT JSONB ENCODING UTF8);
+ERROR: syntax error at or near "ENCODING"
+LINE 1: SELECT JSON_ARRAY(RETURNING text FORMAT JSONB ENCODING UTF8)...
+ ^
+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(RETURNING bytea FORMAT JSONB);
+ json_array
+------------
+ \x00000040
+(1 row)
+
+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(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSONB);
+ json_array
+----------------
+ [[{"a": 123}]]
+(1 row)
+
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING bytea FORMAT JSONB));
+ json_array
+-------------------------------------------------------------------------
+ ["\\x01000040180000d001000020010000800b000010610000002000000000807b00"]
+(1 row)
+
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING bytea FORMAT JSONB) FORMAT JSONB);
+ 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 key "1"
+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 key "1"
+-- 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 FORMAT 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 FORMAT JSON, 2 RETURNING json)
+(2 rows)
+
+CREATE VIEW json_array_view AS
+SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json);
+\sv json_array_view
+CREATE OR REPLACE VIEW public.json_array_view AS
+ SELECT JSON_ARRAY('1'::text FORMAT JSON, 2 RETURNING json) AS "json_array"
+DROP VIEW json_array_view;
+-- Test JSON_OBJECTAGG deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+ Output: JSON_OBJECTAGG(i : (('111'::text || (i)::text))::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE (i > 3))
+ -> Function Scan on pg_catalog.generate_series i
+ Output: i
+ Function Call: generate_series(1, 5)
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) OVER (PARTITION BY i % 2)
+FROM generate_series(1,5) i;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------
+ WindowAgg
+ Output: JSON_OBJECTAGG(i : (('111'::text || (i)::text))::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) OVER (?), ((i % 2))
+ -> Sort
+ Output: ((i % 2)), i
+ Sort Key: ((i.i % 2))
+ -> Function Scan on pg_catalog.generate_series i
+ Output: (i % 2), i
+ Function Call: generate_series(1, 5)
+(8 rows)
+
+CREATE VIEW json_objectagg_view AS
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+\sv json_objectagg_view
+CREATE OR REPLACE VIEW public.json_objectagg_view AS
+ SELECT JSON_OBJECTAGG(i.i : (('111'::text || i.i)::bytea) FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i.i > 3) AS "json_objectagg"
+ FROM generate_series(1, 5) i(i)
+DROP VIEW json_objectagg_view;
+-- Test JSON_ARRAYAGG deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+ Output: JSON_ARRAYAGG((('111'::text || (i)::text))::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE (i > 3))
+ -> Function Scan on pg_catalog.generate_series i
+ Output: i
+ Function Call: generate_series(1, 5)
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (PARTITION BY i % 2)
+FROM generate_series(1,5) i;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------
+ WindowAgg
+ Output: JSON_ARRAYAGG((('111'::text || (i)::text))::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (?), ((i % 2))
+ -> Sort
+ Output: ((i % 2)), i
+ Sort Key: ((i.i % 2))
+ -> Function Scan on pg_catalog.generate_series i
+ Output: (i % 2), i
+ Function Call: generate_series(1, 5)
+(8 rows)
+
+CREATE VIEW json_arrayagg_view AS
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+\sv json_arrayagg_view
+CREATE OR REPLACE VIEW public.json_arrayagg_view AS
+ SELECT JSON_ARRAYAGG((('111'::text || i.i)::bytea) FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i.i > 3) AS "json_arrayagg"
+ FROM generate_series(1, 5) i(i)
+DROP VIEW json_arrayagg_view;
+-- Test JSON_ARRAY(subquery) deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb);
+ QUERY PLAN
+---------------------------------------------------------------------
+ Result
+ Output: $0
+ InitPlan 1 (returns $0)
+ -> Aggregate
+ Output: JSON_ARRAYAGG("*VALUES*".column1 RETURNING jsonb)
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(7 rows)
+
+CREATE VIEW json_array_subquery_view AS
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb);
+\sv json_array_subquery_view
+CREATE OR REPLACE VIEW public.json_array_subquery_view AS
+ SELECT ( SELECT JSON_ARRAYAGG(q.a RETURNING jsonb) AS "json_arrayagg"
+ FROM ( SELECT foo.i
+ FROM ( VALUES (1), (2), (NULL::integer), (4)) foo(i)) q(a)) AS "json_array"
+DROP VIEW json_array_subquery_view;
+-- IS JSON predicate
+SELECT NULL IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL IS NOT JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::json IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::jsonb IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::text IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::bytea IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::int IS JSON;
+ERROR: cannot use type integer in IS JSON predicate
+SELECT '' IS JSON;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT '' FORMAT JSON IS JSON;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT '' FORMAT JSONB IS JSON;
+ERROR: cannot use FORMAT JSONB for string input types
+LINE 1: SELECT '' FORMAT JSONB IS JSON;
+ ^
+SELECT bytea '\x00' IS JSON;
+ERROR: invalid byte sequence for encoding "UTF8": 0x00
+SELECT bytea '\x00' FORMAT JSON IS JSON;
+ERROR: invalid byte sequence for encoding "UTF8": 0x00
+SELECT bytea '\x00' FORMAT JSONB IS JSON;
+ ?column?
+----------
+ f
+(1 row)
+
+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",
+ js FORMAT JSON IS JSON "FORMAT JSON IS JSON"
+FROM
+ test_is_json;
+ js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE | FORMAT JSON IS JSON
+-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------+---------------------
+ | | | | | | | | |
+ | f | t | f | f | f | f | f | f | f
+ 123 | t | f | t | f | f | t | t | t | t
+ "aaa " | t | f | t | f | f | t | t | t | t
+ true | t | f | t | f | f | t | t | t | t
+ null | t | f | t | f | f | t | t | t | t
+ [] | t | f | t | f | t | f | t | t | t
+ [1, "2", {}] | t | f | t | f | t | f | t | t | t
+ {} | t | f | t | t | f | f | t | t | t
+ { "a": 1, "b": null } | t | f | t | t | f | f | t | t | t
+ { "a": 1, "a": null } | t | f | t | t | f | f | t | f | t
+ { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t | t
+ { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f | t
+ aaa | f | t | f | f | f | f | f | f | f
+ {a:1} | f | t | f | f | f | f | f | f | f
+ ["a",] | f | t | f | 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",
+ js FORMAT JSON IS JSON "FORMAT JSON IS JSON"
+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 | FORMAT JSON IS JSON
+-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------+---------------------
+ 123 | t | f | t | f | f | t | t | t | t
+ "aaa " | t | f | t | f | f | t | t | t | t
+ true | t | f | t | f | f | t | t | t | t
+ null | t | f | t | f | f | t | t | t | t
+ [] | t | f | t | f | t | f | t | t | t
+ [1, "2", {}] | t | f | t | f | t | f | t | t | t
+ {} | t | f | t | t | f | f | t | t | t
+ { "a": 1, "b": null } | t | f | t | t | f | f | t | t | t
+ { "a": 1, "a": null } | t | f | t | t | f | f | t | f | t
+ { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t | t
+ { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f | t
+(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",
+ js FORMAT JSON IS JSON "FORMAT JSON IS JSON",
+ js FORMAT JSONB IS JSON "FORMAT JSONB IS JSON"
+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 | FORMAT JSON IS JSON | FORMAT JSONB IS JSON
+-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------+---------------------+----------------------
+ 123 | t | f | t | f | f | t | t | t | t | f
+ "aaa " | t | f | t | f | f | t | t | t | t | f
+ true | t | f | t | f | f | t | t | t | t | f
+ null | t | f | t | f | f | t | t | t | t | f
+ [] | t | f | t | f | t | f | t | t | t | f
+ [1, "2", {}] | t | f | t | f | t | f | t | t | t | f
+ {} | t | f | t | t | f | f | t | t | t | f
+ { "a": 1, "b": null } | t | f | t | t | f | f | t | t | t | f
+ { "a": 1, "a": null } | t | f | t | t | f | f | t | f | t | f
+ { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t | t | f
+ { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | 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",
+ js FORMAT JSON IS JSON "FORMAT JSON IS JSON"
+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 | FORMAT JSON IS JSON
+-------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------+---------------------
+ 123 | t | f | t | f | f | t | t | t | t
+ "aaa " | t | f | t | f | f | t | t | t | t
+ true | t | f | t | f | f | t | t | t | t
+ null | t | f | t | f | f | t | t | t | t
+ [] | t | f | t | f | t | f | t | t | t
+ [1, "2", {}] | t | f | t | f | t | f | t | t | t
+ {} | t | f | t | t | f | f | t | t | t
+ {"a": 1, "b": null} | t | f | t | t | f | f | t | t | t
+ {"a": null} | t | f | t | t | f | f | t | t | t
+ {"a": 1, "b": [{"a": 1}, {"a": 2}]} | t | f | t | t | f | f | t | t | t
+ {"a": 1, "b": [{"a": 2, "b": 0}]} | t | f | t | t | f | f | t | t | t
+(11 rows)
+
+SELECT
+ js0,
+ js FORMAT JSONB IS JSON "IS JSON",
+ js FORMAT JSONB IS NOT JSON "IS NOT JSON",
+ js FORMAT JSONB IS JSON VALUE "IS VALUE",
+ js FORMAT JSONB IS JSON OBJECT "IS OBJECT",
+ js FORMAT JSONB IS JSON ARRAY "IS ARRAY",
+ js FORMAT JSONB IS JSON SCALAR "IS SCALAR",
+ js FORMAT JSONB IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js FORMAT JSONB IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js, js::jsonb::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 | t
+ { "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 | 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/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index b2b171f560..6bebb4a110 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1597,6 +1597,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(65 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
make_timestamp
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 8a4c719993..cdd3c1401e 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1717,6 +1717,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(66 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/input/create_function_1.source b/src/test/regress/input/create_function_1.source
index 223454a5ea..0eb4383196 100644
--- a/src/test/regress/input/create_function_1.source
+++ b/src/test/regress/input/create_function_1.source
@@ -73,6 +73,12 @@ CREATE FUNCTION test_support_func(internal)
AS '@libdir@/regress@DLSUFFIX@', 'test_support_func'
LANGUAGE C STRICT;
+-- Tests creating a custom jsonpath item method
+CREATE FUNCTION array_map(jsonpath_fcxt)
+ RETURNS int8
+ AS '@libdir@/regress@DLSUFFIX@', 'jsonpath_array_map'
+ LANGUAGE C;
+
-- Things that shouldn't work:
CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL
diff --git a/src/test/regress/output/create_function_1.source b/src/test/regress/output/create_function_1.source
index 5f43e8de81..511257e1ee 100644
--- a/src/test/regress/output/create_function_1.source
+++ b/src/test/regress/output/create_function_1.source
@@ -64,6 +64,11 @@ CREATE FUNCTION test_support_func(internal)
RETURNS internal
AS '@libdir@/regress@DLSUFFIX@', 'test_support_func'
LANGUAGE C STRICT;
+-- Tests creating a custom jsonpath item method
+CREATE FUNCTION array_map(jsonpath_fcxt)
+ RETURNS int8
+ AS '@libdir@/regress@DLSUFFIX@', 'jsonpath_array_map'
+ LANGUAGE C;
-- Things that shouldn't work:
CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL
AS 'SELECT ''not an integer'';';
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f23fe8d870..7c9643602f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -99,7 +99,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 json_jsonpath jsonb_jsonpath sqljson json_sqljson jsonb_sqljson
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 7f03b7e857..aa202f375d 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -35,7 +35,8 @@
#include "optimizer/plancat.h"
#include "port/atomics.h"
#include "utils/builtins.h"
-#include "utils/geo_decls.h"
+#include "utils/geo_decls.h"
+#include "utils/jsonpath.h"
#include "utils/rel.h"
#include "utils/typcache.h"
#include "utils/memutils.h"
@@ -940,3 +941,199 @@ test_support_func(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(ret);
}
+
+PG_FUNCTION_INFO_V1(jsonpath_array_map);
+Datum
+jsonpath_array_map(PG_FUNCTION_ARGS)
+{
+ JsonPathFuncContext *fcxt = (JsonPathFuncContext *) PG_GETARG_POINTER(0);
+ JsonPathExecContext *cxt = fcxt->cxt;
+ JsonItem *jb = fcxt->item;
+ JsonPathItem *func = &fcxt->args[jb ? 0 : 1];
+ void **funccache = &fcxt->argscache[jb ? 0 : 1];
+ JsonPathExecResult res;
+ JsonItem *args[3];
+ JsonItem jbvidx;
+ int index = 0;
+ int nargs = 1;
+
+ if (fcxt->nargs != (jb ? 1 : 2))
+ {
+ if (jspThrowErrors(cxt))
+ ereport(ERROR,
+ (errcode(ERRCODE_JSON_SCALAR_REQUIRED),
+ errmsg(ERRMSG_JSON_SCALAR_REQUIRED),
+ errdetail("jsonpath .array_map() requires %d arguments "
+ "but given %d", jb ? 1 : 2, fcxt->nargs)));
+
+ PG_RETURN_INT64(jperError);
+ }
+
+ if (func->type == jpiLambda && func->content.lambda.nparams > 1)
+ {
+ args[nargs++] = &jbvidx;
+ JsonItemGetType(&jbvidx) = jbvNumeric;
+ }
+
+ if (!jb)
+ {
+ JsonValueList items = {0};
+ JsonValueListIterator iter;
+ JsonItem *item;
+
+ res = jspExecuteItem(cxt, &fcxt->args[0], fcxt->jb, &items);
+
+ if (jperIsError(res))
+ PG_RETURN_INT64(res);
+
+ JsonValueListInitIterator(&items, &iter);
+
+ while ((item = JsonValueListNext(&items, &iter)))
+ {
+ JsonValueList reslist = {0};
+
+ args[0] = item;
+
+ if (nargs > 1)
+ {
+ JsonItemNumeric(&jbvidx) = DatumGetNumeric(
+ DirectFunctionCall1(int4_numeric, Int32GetDatum(index)));
+ index++;
+ }
+
+ res = jspExecuteLambda(cxt, func, fcxt->jb, &reslist,
+ args, nargs, funccache);
+
+ if (jperIsError(res))
+ PG_RETURN_INT64(res);
+
+ if (JsonValueListLength(&reslist) != 1)
+ {
+ if (jspThrowErrors(cxt))
+ ereport(ERROR,
+ (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
+ errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED),
+ errdetail("lambda expression in .array_map() "
+ "should return singleton item")));
+
+ PG_RETURN_INT64(jperError);
+ }
+
+ JsonValueListAppend(fcxt->result, JsonValueListHead(&reslist));
+ }
+ }
+ else if (JsonbType(jb) != jbvArray)
+ {
+ JsonValueList reslist = {0};
+
+ if (!jspAutoWrap(cxt))
+ {
+ if (jspThrowErrors(cxt))
+ ereport(ERROR,
+ (errcode(ERRCODE_JSON_ARRAY_NOT_FOUND),
+ errmsg(ERRMSG_JSON_ARRAY_NOT_FOUND),
+ errdetail("jsonpath .array_map() is applied to "
+ "not an array")));
+
+ PG_RETURN_INT64(jperError);
+ }
+
+ args[0] = jb;
+
+ if (nargs > 1)
+ JsonItemNumeric(&jbvidx) = DatumGetNumeric(
+ DirectFunctionCall1(int4_numeric, Int32GetDatum(0)));
+
+ res = jspExecuteLambda(cxt, func, jb, &reslist, args, nargs, funccache);
+
+ if (jperIsError(res))
+ PG_RETURN_INT64(res);
+
+ if (JsonValueListLength(&reslist) != 1)
+ {
+ if (jspThrowErrors(cxt))
+ ereport(ERROR,
+ (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
+ errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED),
+ errdetail("lambda expression in jsonpath .array_map() "
+ "should return singleton item")));
+
+ PG_RETURN_INT64(jperError);
+ }
+
+ JsonValueListAppend(fcxt->result, JsonValueListHead(&reslist));
+ }
+ else
+ {
+ JsonbValue elembuf;
+ JsonbValue *elem;
+ JsonxIterator it;
+ JsonbIteratorToken tok;
+ JsonValueList result = {0};
+ int size = JsonxArraySize(jb, cxt->isJsonb);
+ int i;
+ bool isBinary = JsonItemIsBinary(jb);
+
+ if (isBinary && size > 0)
+ {
+ elem = &elembuf;
+ JsonxIteratorInit(&it, JsonItemBinary(jb).data, cxt->isJsonb);
+ tok = JsonxIteratorNext(&it, &elembuf, false);
+ if (tok != WJB_BEGIN_ARRAY)
+ elog(ERROR, "unexpected jsonb token at the array start");
+ }
+
+ if (nargs > 1)
+ {
+ nargs = 3;
+ args[2] = jb;
+ }
+
+ for (i = 0; i < size; i++)
+ {
+ JsonValueList reslist = {0};
+ JsonItem elemjsi;
+
+ if (isBinary)
+ {
+ tok = JsonxIteratorNext(&it, elem, true);
+ if (tok != WJB_ELEM)
+ break;
+ }
+ else
+ elem = &JsonItemArray(jb).elems[i];
+
+ args[0] = JsonbValueToJsonItem(elem, &elemjsi);
+
+ if (nargs > 1)
+ {
+ JsonItemNumeric(&jbvidx) = DatumGetNumeric(
+ DirectFunctionCall1(int4_numeric, Int32GetDatum(index)));
+ index++;
+ }
+
+ res = jspExecuteLambda(cxt, func, jb, &reslist, args, nargs, funccache);
+
+ if (jperIsError(res))
+ PG_RETURN_INT64(res);
+
+ if (JsonValueListLength(&reslist) != 1)
+ {
+ if (jspThrowErrors(cxt))
+ ereport(ERROR,
+ (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
+ errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED),
+ errdetail("lambda expression in jsonpath .array_map() "
+ "should return singleton item")));
+
+ PG_RETURN_INT64(jperError);
+ }
+
+ JsonValueListConcat(&result, reslist);
+ }
+
+ JsonAppendWrappedItems(fcxt->result, &result, cxt->isJsonb);
+ }
+
+ PG_RETURN_INT64(jperOk);
+}
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index ca200eb599..e7d884a90d 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -165,7 +165,11 @@ test: jsonb
test: json_encoding
test: jsonpath
test: jsonpath_encoding
+test: json_jsonpath
test: jsonb_jsonpath
+test: sqljson
+test: json_sqljson
+test: jsonb_sqljson
test: plancache
test: limit
test: plpgsql
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd563e..3c8580397a 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
new file mode 100644
index 0000000000..66b71903a4
--- /dev/null
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -0,0 +1,534 @@
+select json '{"a": 12}' @? '$';
+select json '{"a": 12}' @? '1';
+select json '{"a": 12}' @? '$.a.b';
+select json '{"a": 12}' @? '$.b';
+select json '{"a": 12}' @? '$.a + 2';
+select json '{"a": 12}' @? '$.b + 2';
+select json '{"a": {"a": 12}}' @? '$.a.a';
+select json '{"a": {"a": 12}}' @? '$.*.a';
+select json '{"b": {"a": 12}}' @? '$.*.a';
+select json '{"b": {"a": 12}}' @? '$.*.b';
+select json '{"b": {"a": 12}}' @? 'strict $.*.b';
+select json '{}' @? '$.*';
+select json '{"a": 1}' @? '$.*';
+select json '{"a": {"b": 1}}' @? 'lax $.**{1}';
+select json '{"a": {"b": 1}}' @? 'lax $.**{2}';
+select json '{"a": {"b": 1}}' @? 'lax $.**{3}';
+select json '[]' @? '$[*]';
+select json '[1]' @? '$[*]';
+select json '[1]' @? '$[1]';
+select json '[1]' @? 'strict $[1]';
+select json_path_query('[1]', 'strict $[1]');
+select json '[1]' @? 'lax $[10000000000000000]';
+select json '[1]' @? 'strict $[10000000000000000]';
+select json_path_query('[1]', 'lax $[10000000000000000]');
+select json_path_query('[1]', 'strict $[10000000000000000]');
+select json '[1]' @? '$[0]';
+select json '[1]' @? '$[0.3]';
+select json '[1]' @? '$[0.5]';
+select json '[1]' @? '$[0.9]';
+select json '[1]' @? '$[1.2]';
+select json '[1]' @? 'strict $[1.2]';
+select json_path_query('[1]', 'strict $[1.2]');
+select json_path_query('{}', 'strict $[0.3]');
+select json '{}' @? 'lax $[0.3]';
+select json_path_query('{}', 'strict $[1.2]');
+select json '{}' @? 'lax $[1.2]';
+select json_path_query('{}', 'strict $[-2 to 3]');
+select json '{}' @? 'lax $[-2 to 3]';
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+select json '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+select json '1' @? '$ ? ((@ == "1") is unknown)';
+select json '1' @? '$ ? ((@ == 1) is unknown)';
+select json '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+
+select json_path_query('1', 'lax $.a');
+select json_path_query('1', 'strict $.a');
+select json_path_query('1', 'strict $.*');
+select json_path_query('[]', 'lax $.a');
+select json_path_query('[]', 'strict $.a');
+select json_path_query('{}', 'lax $.a');
+select json_path_query('{}', 'strict $.a');
+
+select json_path_query('1', 'strict $[1]');
+select json_path_query('1', 'strict $[*]');
+select json_path_query('[]', 'strict $[1]');
+select json_path_query('[]', 'strict $["a"]');
+
+select json_path_query('{"a": 12, "b": {"a": 13}}', '$.a');
+select json_path_query('{"a": 12, "b": {"a": 13}}', '$.b');
+select json_path_query('{"a": 12, "b": {"a": 13}}', '$.*');
+select json_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10 / 0].a');
+select json_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
+select json_path_query('1', 'lax $[0]');
+select json_path_query('1', 'lax $[*]');
+select json_path_query('{}', 'lax $[0]');
+select json_path_query('[1]', 'lax $[0]');
+select json_path_query('[1]', 'lax $[*]');
+select json_path_query('[1,2,3]', 'lax $[*]');
+select json_path_query('[1,2,3]', 'strict $[*].a');
+select json_path_query('[]', '$[last]');
+select json_path_query('[]', '$[last ? (exists(last))]');
+select json_path_query('[]', 'strict $[last]');
+select json_path_query('[1]', '$[last]');
+select json_path_query('{}', '$[last]');
+select json_path_query('[1,2,3]', '$[last]');
+select json_path_query('[1,2,3]', '$[last - 1]');
+select json_path_query('[1,2,3]', '$[last ? (@.type() == "number")]');
+select json_path_query('[1,2,3]', '$[last ? (@.type() == "string")]');
+
+select * from json_path_query('{"a": 10}', '$');
+select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)');
+select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '1');
+select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '[{"value" : 13}]');
+select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 13}');
+select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 8}');
+select * from json_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+select * from json_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+select * from json_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $x.value)', '{"x": {"value" : 13}}');
+select * from json_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+select * from json_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+select * from json_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+select * from json_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : null}');
+select * from json_path_query('[1, "2", null]', '$[*] ? (@ != null)');
+select * from json_path_query('[1, "2", null]', '$[*] ? (@ == null)');
+select * from json_path_query('{}', '$ ? (@ == @)');
+select * from json_path_query('[]', 'strict $ ? (@ == @)');
+
+select json_path_query('{"a": {"b": 1}}', 'lax $.**');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{0}');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{1}');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{2}');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{last}');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select json_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+
+
+select json '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+
+select json_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))');
+select json_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))');
+select json_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? (exists (@.x))');
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? (exists (@.x + "3"))');
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? ((exists (@.x + "3")) is unknown)');
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g[*] ? (exists (@.x))');
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g[*] ? ((exists (@.x)) is unknown)');
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g ? (exists (@[*].x))');
+select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g ? ((exists (@[*].x)) is unknown)');
+
+--test ternary logic
+select
+ x, y,
+ json_path_query(
+ '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true && $y == true) ||
+ @ == false && !($x == true && $y == true) ||
+ @ == null && ($x == true && $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x && y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+
+select
+ x, y,
+ json_path_query(
+ '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true || $y == true) ||
+ @ == false && !($x == true || $y == true) ||
+ @ == null && ($x == true || $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x || y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+
+select json '{"a": 1, "b":1}' @? '$ ? (@.a == @.b)';
+select json '{"c": {"a": 1, "b":1}}' @? '$ ? (@.a == @.b)';
+select json '{"c": {"a": 1, "b":1}}' @? '$.c ? (@.a == @.b)';
+select json '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == @.b)';
+select json '{"c": {"a": 1, "b":1}}' @? '$.* ? (@.a == @.b)';
+select json '{"a": 1, "b":1}' @? '$.** ? (@.a == @.b)';
+select json '{"c": {"a": 1, "b":1}}' @? '$.** ? (@.a == @.b)';
+
+select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == 1 + 1)');
+select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (1 + 1))');
+select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == @.b + 1)');
+select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (@.b + 1))');
+select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - 1)';
+select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -1)';
+select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -@.b)';
+select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - @.b)';
+select json '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - @.b)';
+select json '{"c": {"a": 2, "b":1}}' @? '$.** ? (@.a == 1 - - @.b)';
+select json '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - +@.b)';
+select json '[1,2,3]' @? '$ ? (+@[*] > +2)';
+select json '[1,2,3]' @? '$ ? (+@[*] > +3)';
+select json '[1,2,3]' @? '$ ? (-@[*] < -2)';
+select json '[1,2,3]' @? '$ ? (-@[*] < -3)';
+select json '1' @? '$ ? ($ > 0)';
+
+-- arithmetic errors
+select json_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+select json_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+select json_path_query('0', '1 / $');
+select json_path_query('0', '1 / $ + 2');
+select json_path_query('0', '-(3 + 1 % $)');
+select json_path_query('1', '$ + "2"');
+select json_path_query('[1, 2]', '3 * $');
+select json_path_query('"a"', '-$');
+select json_path_query('[1,"2",3]', '+$');
+select json '["1",2,0,3]' @? '-$[*]';
+select json '[1,"2",0,3]' @? '-$[*]';
+select json '["1",2,0,3]' @? 'strict -$[*]';
+select json '[1,"2",0,3]' @? 'strict -$[*]';
+
+-- unwrapping of operator arguments in lax mode
+select json_path_query('{"a": [2]}', 'lax $.a * 3');
+select json_path_query('{"a": [2]}', 'lax $.a + 3');
+select json_path_query('{"a": [2, 3, 4]}', 'lax -$.a');
+-- should fail
+select json_path_query('{"a": [1, 2]}', 'lax $.a * 3');
+
+-- extension: boolean expressions
+select json_path_query('2', '$ > 1');
+select json_path_query('2', '$ <= 1');
+select json_path_query('2', '$ == "2"');
+select json '2' @? '$ == "2"';
+
+select json '2' @@ '$ > 1';
+select json '2' @@ '$ <= 1';
+select json '2' @@ '$ == "2"';
+select json '2' @@ '1';
+select json '{}' @@ '$';
+select json '[]' @@ '$';
+select json '[1,2,3]' @@ '$[*]';
+select json '[]' @@ '$[*]';
+select json_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+select json_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+
+select json_path_query('[null,1,true,"a",[],{}]', '$.type()');
+select json_path_query('[null,1,true,"a",[],{}]', 'lax $.type()');
+select json_path_query('[null,1,true,"a",[],{}]', '$[*].type()');
+select json_path_query('null', 'null.type()');
+select json_path_query('null', 'true.type()');
+select json_path_query('null', '123.type()');
+select json_path_query('null', '"123".type()');
+
+select json_path_query('{"a": 2}', '($.a - 5).abs() + 10');
+select json_path_query('{"a": 2.5}', '-($.a * $.a).floor() % 4.3');
+select json_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)');
+select json_path_query('[1, 2, 3]', '($[*] > 3).type()');
+select json_path_query('[1, 2, 3]', '($[*].a > 3).type()');
+select json_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()');
+
+select json_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
+select json_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');
+
+select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
+select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()');
+select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()');
+select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()');
+select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()');
+
+select json_path_query('[{},1]', '$[*].keyvalue()');
+select json_path_query('{}', '$.keyvalue()');
+select json_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()');
+select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()');
+select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()');
+select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()');
+select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue().a');
+select json '{"a": 1, "b": [1, 2]}' @? 'lax $.keyvalue()';
+select json '{"a": 1, "b": [1, 2]}' @? 'lax $.keyvalue().key';
+
+select json_path_query('null', '$.double()');
+select json_path_query('true', '$.double()');
+select json_path_query('[]', '$.double()');
+select json_path_query('[]', 'strict $.double()');
+select json_path_query('{}', '$.double()');
+select json_path_query('1.23', '$.double()');
+select json_path_query('"1.23"', '$.double()');
+select json_path_query('"1.23aaa"', '$.double()');
+select json_path_query('"nan"', '$.double()');
+select json_path_query('"NaN"', '$.double()');
+select json_path_query('"inf"', '$.double()');
+select json_path_query('"-inf"', '$.double()');
+
+select json_path_query('{}', '$.abs()');
+select json_path_query('true', '$.floor()');
+select json_path_query('"1.2"', '$.ceiling()');
+
+select json_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
+select json_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
+select json_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
+select json_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
+select json_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
+select json_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
+select json_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
+select json_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
+
+select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")');
+select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^a b.* c " flag "ix")');
+select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "m")');
+select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "s")');
+
+select json_path_query('null', '$.datetime()');
+select json_path_query('true', '$.datetime()');
+select json_path_query('[]', '$.datetime()');
+select json_path_query('[]', 'strict $.datetime()');
+select json_path_query('{}', '$.datetime()');
+select json_path_query('""', '$.datetime()');
+select json_path_query('"12:34"', '$.datetime("aaa")');
+select json_path_query('"12:34"', '$.datetime("aaa", 1)');
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 1)');
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)');
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)');
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483647)');
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483648)');
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483647)');
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483648)');
+select json_path_query('"aaaa"', '$.datetime("HH24")');
+
+-- Standard extension: UNIX epoch to timestamptz
+select json_path_query('0', '$.datetime()');
+select json_path_query('0', '$.datetime().type()');
+select json_path_query('1490216035.5', '$.datetime()');
+
+select json '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+select json_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select json_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select json_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+select json_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select json_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select json_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", 12 * 60)');
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", -(5 * 3600 + 12 * 60 + 34))');
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)',
+ json_build_object('tz', extract(timezone from now())));
+select json_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select json_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select json_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select json_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select json_path_query('"12:34"', '$.datetime("HH24:MI")');
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+select json_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select json_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select json_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select json_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)',
+ json_build_object('tz', extract(timezone from now())));
+select json_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select json_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select json_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select json_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select json_path_query('"12:34"', '$.datetime("HH24:MI")');
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+select json_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select json_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select json_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select json_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select json_path_query('"2017-03-10"', '$.datetime().type()');
+select json_path_query('"2017-03-10"', '$.datetime()');
+select json_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select json_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select json_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select json_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select json_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select json_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select json_path_query('"12:34:56"', '$.datetime().type()');
+select json_path_query('"12:34:56"', '$.datetime()');
+select json_path_query('"12:34:56 +3"', '$.datetime().type()');
+select json_path_query('"12:34:56 +3"', '$.datetime()');
+select json_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select json_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select json_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select json_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select json_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select json_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select json_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select json_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select json_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select json_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select json_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select json_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select json_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select json_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select json_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select json_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select json_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
+-- jsonpath operators
+
+SELECT json_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
+SELECT json_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+SELECT json_path_query('[{"a": 1}, {"a": 2}]', '[$[*].a]');
+
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '[$[*].a]');
+
+SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
+SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a');
+SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+
+SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
+SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
+SELECT json_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 1, "max": 4}');
+SELECT json_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 3, "max": 4}');
+
+SELECT json '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1';
+SELECT json '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2';
+
+-- extension: path sequences
+select json_path_query('[1,2,3,4,5]', '10, 20, $[*], 30');
+select json_path_query('[1,2,3,4,5]', 'lax 10, 20, $[*].a, 30');
+select json_path_query('[1,2,3,4,5]', 'strict 10, 20, $[*].a, 30');
+select json_path_query('[1,2,3,4,5]', '-(10, 20, $[1 to 3], 30)');
+select json_path_query('[1,2,3,4,5]', 'lax (10, 20.5, $[1 to 3], "30").double()');
+select json_path_query('[1,2,3,4,5]', '$[(0, $[*], 5) ? (@ == 3)]');
+select json_path_query('[1,2,3,4,5]', '$[(0, $[*], 3) ? (@ == 3)]');
+
+-- extension: array constructors
+select json_path_query('[1, 2, 3]', '[]');
+select json_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5]');
+select json_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5][*]');
+select json_path_query('[1, 2, 3]', '[(1, (2, $[*])), (4, 5)]');
+select json_path_query('[1, 2, 3]', '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]');
+select json_path_query('[1, 2, 3]', 'strict [1, 2, $[*].a, 4, 5]');
+select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '[$[*][*] ? (@ > 3)]');
+
+-- extension: object constructors
+select json_path_query('[1, 2, 3]', '{}');
+select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}');
+select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}.*');
+select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}[*]');
+select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": ($[*], 4, 5)}');
+select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}');
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+select json '{"a": 1}' @? '$["b"]';
+select json '{"a": 1}' @? 'strict $["b"]';
+select json '{"a": 1}' @? '$["b", "a"]';
+
+select json_path_query('{"a": 1}', '$["a"]');
+select json_path_query('{"a": 1}', 'strict $["b"]');
+select json_path_query('{"a": 1}', 'lax $["b"]');
+select json_path_query('{"a": 1, "b": 2}', 'lax $["b", "c", "b", "a", 0 to 3]');
+
+select json_path_query('null', '{"a": 1}["a"]');
+select json_path_query('null', '{"a": 1}["b"]');
diff --git a/src/test/regress/sql/json_sqljson.sql b/src/test/regress/sql/json_sqljson.sql
new file mode 100644
index 0000000000..a3b29584a7
--- /dev/null
+++ b/src/test/regress/sql/json_sqljson.sql
@@ -0,0 +1,905 @@
+-- JSON_EXISTS
+
+SELECT JSON_EXISTS(NULL FORMAT JSON, '$');
+SELECT JSON_EXISTS(NULL::text FORMAT JSON, '$');
+SELECT JSON_EXISTS(NULL::bytea FORMAT JSON, '$');
+SELECT JSON_EXISTS(NULL::json FORMAT JSON, '$');
+SELECT JSON_EXISTS(NULL::jsonb FORMAT JSON, '$');
+SELECT JSON_EXISTS(NULL::json, '$');
+
+SELECT JSON_EXISTS('' FORMAT JSON, '$');
+SELECT JSON_EXISTS('' FORMAT JSON, '$' TRUE ON ERROR);
+SELECT JSON_EXISTS('' FORMAT JSON, '$' FALSE ON ERROR);
+SELECT JSON_EXISTS('' FORMAT JSON, '$' UNKNOWN ON ERROR);
+SELECT JSON_EXISTS('' FORMAT JSON, '$' ERROR ON ERROR);
+
+
+SELECT JSON_EXISTS(bytea '' FORMAT JSON, '$' ERROR ON ERROR);
+
+SELECT JSON_EXISTS(json '[]', '$');
+SELECT JSON_EXISTS('[]' FORMAT JSON, '$');
+SELECT JSON_EXISTS(JSON_OBJECT(RETURNING bytea FORMAT JSON) FORMAT JSON, '$');
+
+SELECT JSON_EXISTS(json '1', '$');
+SELECT JSON_EXISTS(json 'null', '$');
+SELECT JSON_EXISTS(json '[]', '$');
+
+SELECT JSON_EXISTS(json '1', '$.a');
+SELECT JSON_EXISTS(json '1', 'strict $.a');
+SELECT JSON_EXISTS(json '1', 'strict $.a' ERROR ON ERROR);
+SELECT JSON_EXISTS(json 'null', '$.a');
+SELECT JSON_EXISTS(json '[]', '$.a');
+SELECT JSON_EXISTS(json '[1, "aaa", {"a": 1}]', 'strict $.a');
+SELECT JSON_EXISTS(json '[1, "aaa", {"a": 1}]', 'lax $.a');
+SELECT JSON_EXISTS(json '{}', '$.a');
+SELECT JSON_EXISTS(json '{"b": 1, "a": 2}', '$.a');
+
+SELECT JSON_EXISTS(json '1', '$.a.b');
+SELECT JSON_EXISTS(json '{"a": {"b": 1}}', '$.a.b');
+SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.a.b');
+
+SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING 1 AS x);
+SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING '1' AS x);
+SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 2 AS y);
+SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 1 AS y);
+
+-- extension: boolean expressions
+SELECT JSON_EXISTS(json '1', '$ > 2');
+SELECT JSON_EXISTS(json '1', '$.a > 2' ERROR ON ERROR);
+
+-- JSON_VALUE
+
+SELECT JSON_VALUE(NULL, '$');
+SELECT JSON_VALUE(NULL FORMAT JSON, '$');
+SELECT JSON_VALUE(NULL::text, '$');
+SELECT JSON_VALUE(NULL::bytea, '$');
+SELECT JSON_VALUE(NULL::json, '$');
+SELECT JSON_VALUE(NULL::jsonb FORMAT JSON, '$');
+
+SELECT JSON_VALUE('' FORMAT JSON, '$');
+SELECT JSON_VALUE('' FORMAT JSON, '$' NULL ON ERROR);
+SELECT JSON_VALUE('' FORMAT JSON, '$' DEFAULT '"default value"' ON ERROR);
+SELECT JSON_VALUE('' FORMAT JSON, '$' ERROR ON ERROR);
+
+SELECT JSON_VALUE(json 'null', '$');
+SELECT JSON_VALUE(json 'null', '$' RETURNING int);
+
+SELECT JSON_VALUE(json 'true', '$');
+SELECT JSON_VALUE(json 'true', '$' RETURNING bool);
+
+SELECT JSON_VALUE(json '123', '$');
+SELECT JSON_VALUE(json '123', '$' RETURNING int) + 234;
+SELECT JSON_VALUE(json '123', '$' RETURNING text);
+/* jsonb bytea ??? */
+SELECT JSON_VALUE(json '123', '$' RETURNING bytea ERROR ON ERROR);
+
+SELECT JSON_VALUE(json '1.23', '$');
+SELECT JSON_VALUE(json '1.23', '$' RETURNING int);
+SELECT JSON_VALUE(json '"1.23"', '$' RETURNING numeric);
+SELECT JSON_VALUE(json '"1.23"', '$' RETURNING int ERROR ON ERROR);
+
+SELECT JSON_VALUE(json '"aaa"', '$');
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING text);
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING char(5));
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING char(2));
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING json);
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING jsonb);
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING json ERROR ON ERROR);
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING jsonb ERROR ON ERROR);
+SELECT JSON_VALUE(json '"\"aaa\""', '$' RETURNING json);
+SELECT JSON_VALUE(json '"\"aaa\""', '$' RETURNING jsonb);
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING int);
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING int ERROR ON ERROR);
+SELECT JSON_VALUE(json '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR);
+SELECT JSON_VALUE(json '"123"', '$' RETURNING int) + 234;
+
+SELECT JSON_VALUE(json '"2017-02-20"', '$' RETURNING date) + 9;
+
+-- Test NULL checks execution in domain types
+CREATE DOMAIN sqljson_int_not_null AS int NOT NULL;
+SELECT JSON_VALUE(json '1', '$.a' RETURNING sqljson_int_not_null);
+SELECT JSON_VALUE(json '1', '$.a' RETURNING sqljson_int_not_null NULL ON ERROR);
+SELECT JSON_VALUE(json '1', '$.a' RETURNING sqljson_int_not_null DEFAULT NULL ON ERROR);
+
+SELECT JSON_VALUE(json '[]', '$');
+SELECT JSON_VALUE(json '[]', '$' ERROR ON ERROR);
+SELECT JSON_VALUE(json '{}', '$');
+SELECT JSON_VALUE(json '{}', '$' ERROR ON ERROR);
+
+SELECT JSON_VALUE(json '1', '$.a');
+SELECT JSON_VALUE(json '1', 'strict $.a' ERROR ON ERROR);
+SELECT JSON_VALUE(json '1', 'strict $.a' DEFAULT 'error' ON ERROR);
+SELECT JSON_VALUE(json '1', 'lax $.a' ERROR ON ERROR);
+SELECT JSON_VALUE(json '1', 'lax $.a' ERROR ON EMPTY ERROR ON ERROR);
+SELECT JSON_VALUE(json '1', 'strict $.a' DEFAULT 2 ON ERROR);
+SELECT JSON_VALUE(json '1', 'lax $.a' DEFAULT 2 ON ERROR);
+SELECT JSON_VALUE(json '1', 'lax $.a' DEFAULT '2' ON ERROR);
+SELECT JSON_VALUE(json '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR);
+SELECT JSON_VALUE(json '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR);
+SELECT JSON_VALUE(json '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR);
+
+SELECT JSON_VALUE(json '[1,2]', '$[*]' ERROR ON ERROR);
+SELECT JSON_VALUE(json '[1,2]', '$[*]' DEFAULT '0' ON ERROR);
+SELECT JSON_VALUE(json '[" "]', '$[*]' RETURNING int ERROR ON ERROR);
+SELECT JSON_VALUE(json '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR);
+SELECT JSON_VALUE(json '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR);
+
+SELECT
+ x,
+ JSON_VALUE(
+ json '{"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(json 'null', '$a' PASSING point ' (1, 2 )' AS a);
+SELECT JSON_VALUE(json 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point);
+
+-- Test timestamptz passing and output
+SELECT JSON_VALUE(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
+SELECT JSON_VALUE(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamptz);
+SELECT JSON_VALUE(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamp);
+SELECT JSON_VALUE(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json);
+SELECT JSON_VALUE(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb);
+
+-- JSON_QUERY
+
+SELECT
+ JSON_QUERY(js FORMAT JSON, '$'),
+ JSON_QUERY(js FORMAT JSON, '$' WITHOUT WRAPPER),
+ JSON_QUERY(js FORMAT JSON, '$' WITH CONDITIONAL WRAPPER),
+ JSON_QUERY(js FORMAT JSON, '$' WITH UNCONDITIONAL ARRAY WRAPPER),
+ JSON_QUERY(js FORMAT JSON, '$' WITH ARRAY WRAPPER)
+FROM
+ (VALUES
+ ('null'),
+ ('12.3'),
+ ('true'),
+ ('"aaa"'),
+ ('[1, null, "2"]'),
+ ('{"a": 1, "b": [2]}')
+ ) foo(js);
+
+SELECT
+ JSON_QUERY(js FORMAT JSON, 'strict $[*]') AS "unspec",
+ JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITHOUT WRAPPER) AS "without",
+ JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond",
+ JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond",
+ JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITH ARRAY WRAPPER) AS "with"
+FROM
+ (VALUES
+ ('1'),
+ ('[]'),
+ ('[null]'),
+ ('[12.3]'),
+ ('[true]'),
+ ('["aaa"]'),
+ ('[[1, 2, 3]]'),
+ ('[{"a": 1, "b": [2]}]'),
+ ('[1, "2", null, [3]]')
+ ) foo(js);
+
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text);
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text KEEP QUOTES);
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text KEEP QUOTES ON SCALAR STRING);
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text OMIT QUOTES);
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text OMIT QUOTES ON SCALAR STRING);
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' OMIT QUOTES ERROR ON ERROR);
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING json OMIT QUOTES ERROR ON ERROR);
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR);
+SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING bytea FORMAT JSONB OMIT QUOTES ERROR ON ERROR);
+
+-- QUOTES behavior should not be specified when WITH WRAPPER used:
+-- Should fail
+SELECT JSON_QUERY(json '[1]', '$' WITH WRAPPER OMIT QUOTES);
+SELECT JSON_QUERY(json '[1]', '$' WITH WRAPPER KEEP QUOTES);
+SELECT JSON_QUERY(json '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTES);
+SELECT JSON_QUERY(json '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES);
+-- Should succeed
+SELECT JSON_QUERY(json '[1]', '$' WITHOUT WRAPPER OMIT QUOTES);
+SELECT JSON_QUERY(json '[1]', '$' WITHOUT WRAPPER KEEP QUOTES);
+
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]');
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' NULL ON EMPTY);
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' EMPTY ARRAY ON EMPTY);
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' EMPTY OBJECT ON EMPTY);
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY);
+
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY NULL ON ERROR);
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR);
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR);
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY ERROR ON ERROR);
+SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON ERROR);
+
+SELECT JSON_QUERY('[1,2]' FORMAT JSON, '$[*]' ERROR ON ERROR);
+
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING json);
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING json FORMAT JSON);
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING jsonb);
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING jsonb FORMAT JSON);
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text);
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING char(10));
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING char(3));
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text FORMAT JSON);
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text FORMAT JSONB);
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea);
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea FORMAT JSON);
+SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea FORMAT JSONB);
+
+SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR);
+SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT ON ERROR);
+SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea FORMAT JSONB EMPTY OBJECT ON ERROR);
+SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR);
+SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING jsonb EMPTY OBJECT ON ERROR);
+
+SELECT
+ x, y,
+ JSON_QUERY(
+ json '[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 sqljson_rec AS (a int, t text, js json, jb jsonb, jsa json[]);
+CREATE TYPE sqljson_reca AS (reca sqljson_rec[]);
+
+SELECT JSON_QUERY(json '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljson_rec);
+SELECT * FROM unnest((JSON_QUERY(json '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljson_rec)).jsa);
+SELECT * FROM unnest((JSON_QUERY(json '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljson_reca)).reca);
+
+-- Extension: array types returning
+SELECT JSON_QUERY(json '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER);
+SELECT * FROM unnest(JSON_QUERY(json '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljson_rec[]));
+
+-- Extension: domain types returning
+SELECT JSON_QUERY(json '{"a": 1}', '$.a' RETURNING sqljson_int_not_null);
+SELECT JSON_QUERY(json '{"a": 1}', '$.b' RETURNING sqljson_int_not_null);
+
+-- Test timestamptz passing and output
+SELECT JSON_QUERY(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
+SELECT JSON_QUERY(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json);
+SELECT JSON_QUERY(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb);
+
+-- Test constraints
+
+CREATE TABLE test_json_constraints (
+ js text,
+ i int,
+ x jsonb DEFAULT JSON_QUERY(json '[1,2]', '$[*]' WITH WRAPPER)
+ CONSTRAINT test_json_constraint1
+ CHECK (js IS JSON)
+ CONSTRAINT test_json_constraint2
+ CHECK (JSON_EXISTS(js FORMAT JSON, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr))
+ CONSTRAINT test_json_constraint3
+ CHECK (JSON_VALUE(js::json, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i)
+ CONSTRAINT test_json_constraint4
+ CHECK (JSON_QUERY(js FORMAT JSON, '$.a' RETURNING jsonb WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]')
+ CONSTRAINT test_json_constraint5
+ CHECK (JSON_QUERY(js FORMAT JSON, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a')
+);
+
+\d test_json_constraints
+
+SELECT check_clause
+FROM information_schema.check_constraints
+WHERE constraint_name LIKE 'test_json_constraint%';
+
+SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'test_json_constraints'::regclass;
+
+INSERT INTO test_json_constraints VALUES ('', 1);
+INSERT INTO test_json_constraints VALUES ('1', 1);
+INSERT INTO test_json_constraints VALUES ('[]');
+INSERT INTO test_json_constraints VALUES ('{"b": 1}', 1);
+INSERT INTO test_json_constraints VALUES ('{"a": 1}', 1);
+INSERT INTO test_json_constraints VALUES ('{"a": 7}', 1);
+INSERT INTO test_json_constraints VALUES ('{"a": 10}', 1);
+
+DROP TABLE test_json_constraints;
+
+-- JSON_TABLE
+
+-- Should fail (JSON_TABLE can be used only in FROM clause)
+SELECT JSON_TABLE('[]', '$');
+
+-- Should fail (no columns)
+SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ());
+
+-- NULL => empty table
+SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS (foo int)) bar;
+
+-- invalid json => empty table
+SELECT * FROM JSON_TABLE('', '$' COLUMNS (foo int)) bar;
+SELECT * FROM JSON_TABLE('' FORMAT JSON, '$' COLUMNS (foo int)) bar;
+
+-- invalid json => error
+SELECT * FROM JSON_TABLE('' FORMAT JSON, '$' COLUMNS (foo int) ERROR ON ERROR) bar;
+
+--
+SELECT * FROM JSON_TABLE('123' FORMAT JSON, '$'
+ COLUMNS (item int PATH '$', foo int)) bar;
+
+SELECT * FROM JSON_TABLE(json '123', '$'
+ COLUMNS (item int PATH '$', foo int)) bar;
+
+-- JSON_TABLE: basic functionality
+SELECT *
+FROM
+ (VALUES
+ ('1'),
+ ('[]'),
+ ('{}'),
+ ('[1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]'),
+ ('err')
+ ) vals(js)
+ LEFT OUTER JOIN
+-- JSON_TABLE is implicitly lateral
+ JSON_TABLE(
+ vals.js FORMAT json, 'lax $[*]'
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY, -- allowed additional ordinality columns
+ "int" int PATH '$',
+ "text" text PATH '$',
+ "char(4)" char(4) PATH '$',
+ "bool" bool PATH '$',
+ "numeric" numeric PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc char(4) FORMAT JSON PATH '$',
+ jsv varchar(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSONB PATH '$',
+ aaa int, -- implicit path '$."aaa"',
+ aaa1 int PATH '$.aaa'
+ )
+ ) jt
+ ON true;
+
+-- JSON_TABLE: Test backward parsing
+
+CREATE VIEW json_table_view AS
+SELECT * FROM
+ JSON_TABLE(
+ 'null' FORMAT JSON, 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY, -- allowed additional ordinality columns
+ "int" int PATH '$',
+ "text" text PATH '$',
+ "char(4)" char(4) PATH '$',
+ "bool" bool PATH '$',
+ "numeric" numeric PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc char(4) FORMAT JSON PATH '$',
+ jsv varchar(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSONB PATH '$',
+ aaa int, -- implicit path '$."aaa"',
+ aaa1 int PATH '$.aaa',
+ NESTED PATH '$[1]' AS p1 COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' AS "p1 1" COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' AS p2 COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1" COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' AS p22 COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
+
+\sv json_table_view
+
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM json_table_view;
+
+-- JSON_TABLE: ON EMPTY/ON ERROR behavior
+SELECT *
+FROM
+ (VALUES ('1'), ('err'), ('"err"')) vals(js),
+ JSON_TABLE(vals.js FORMAT JSON, '$' COLUMNS (a int PATH '$')) jt;
+
+SELECT *
+FROM
+ (VALUES ('1'), ('err'), ('"err"')) vals(js)
+ LEFT OUTER JOIN
+ JSON_TABLE(vals.js FORMAT JSON, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt
+ ON true;
+
+SELECT *
+FROM
+ (VALUES ('1'), ('err'), ('"err"')) vals(js)
+ LEFT OUTER JOIN
+ JSON_TABLE(vals.js FORMAT JSON, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
+ ON true;
+
+SELECT * FROM JSON_TABLE('1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
+SELECT * FROM JSON_TABLE('1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+SELECT * FROM JSON_TABLE('1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+
+SELECT * FROM JSON_TABLE(json '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(json '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(json '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+
+-- JSON_TABLE: nested paths and plans
+
+-- Should fail (JSON_TABLE columns shall contain explicit AS path
+-- specifications if explicit PLAN clause is used)
+SELECT * FROM JSON_TABLE(
+ json '[]', '$' -- AS required here
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS ( -- AS required here
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+
+-- Should fail (column names anf path names shall be distinct)
+SELECT * FROM JSON_TABLE(
+ json '[]', '$' AS a
+ COLUMNS (
+ a int
+ )
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json '[]', '$' AS a
+ COLUMNS (
+ b int,
+ NESTED PATH '$' AS a
+ COLUMNS (
+ c int
+ )
+ )
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json '[]', '$'
+ COLUMNS (
+ b int,
+ NESTED PATH '$' AS b
+ COLUMNS (
+ c int
+ )
+ )
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json '[]', '$'
+ COLUMNS (
+ NESTED PATH '$' AS a
+ COLUMNS (
+ b int
+ ),
+ NESTED PATH '$'
+ COLUMNS (
+ NESTED PATH '$' AS a
+ COLUMNS (
+ c int
+ )
+ )
+ )
+) jt;
+
+-- JSON_TABLE: plan validation
+
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p1)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER p3)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 UNION p1 UNION p11)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p13))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER p11) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json 'null', 'strict $[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ json 'null', 'strict $[*]' -- without root path name
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))
+) jt;
+
+-- JSON_TABLE: plan execution
+
+CREATE TEMP TABLE json_table_test (js text);
+
+INSERT INTO json_table_test
+VALUES (
+ '[
+ {"a": 1, "b": [], "c": []},
+ {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]},
+ {"a": 3, "b": [1, 2], "c": []},
+ {"x": "4", "b": [1, 2], "c": 123}
+ ]'
+);
+
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ ) jt;
+
+-- default plan (outer, union)
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, union)
+ ) jt;
+
+-- specific plan (p outer (pb union pc))
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb union pc))
+ ) jt;
+
+-- specific plan (p outer (pc union pb))
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pc union pb))
+ ) jt;
+
+-- default plan (inner, union)
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner)
+ ) jt;
+
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb union pc))
+ ) jt;
+
+-- default plan (inner, cross)
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (cross, inner)
+ ) jt;
+
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb cross pc))
+ ) jt;
+
+-- default plan (outer, cross)
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt;
+
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ json_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSON,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb cross pc))
+ ) jt;
+
+
+select
+ jt.*, b1 + 100 as b
+from
+ json_table (json
+ '[
+ {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]},
+ {"a": 2, "b": [10, 20], "c": [1, null, 2]},
+ {"x": "3", "b": [11, 22, 33, 44]}
+ ]',
+ '$[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on error,
+ nested path 'strict $.b[*]' as pb columns (
+ b text format json path '$',
+ nested path 'strict $[*]' as pb1 columns (
+ b1 int path '$'
+ )
+ ),
+ nested path 'strict $.c[*]' as pc columns (
+ c text format json path '$',
+ nested path 'strict $[*]' as pc1 columns (
+ c1 int path '$'
+ )
+ )
+ )
+ --plan default(outer, cross)
+ plan(p outer ((pb inner pb1) cross (pc outer pc1)))
+ ) jt;
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(json
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >= $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ json '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+
+-- Extension: non-constant JSON path
+SELECT JSON_EXISTS(json '{"a": 123}', '$' || '.' || 'a');
+SELECT JSON_VALUE(json '{"a": 123}', '$' || '.' || 'a');
+SELECT JSON_VALUE(json '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY);
+SELECT JSON_QUERY(json '{"a": 123}', '$' || '.' || 'a');
+SELECT JSON_QUERY(json '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
+-- Should fail (invalid path)
+SELECT JSON_QUERY(json '{"a": 123}', 'error' || ' ' || 'error');
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(json '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index ba870872e8..048bee66f6 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1250,3 +1250,9 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- test jsonb to/from bytea conversion
+SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea;
+SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea::jsonb;
+SELECT 'aaaa'::bytea::jsonb;
+SELECT count(*) FROM testjsonb WHERE j::bytea::jsonb <> j;
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 464ff94be3..ea6aea5c9c 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -30,6 +30,14 @@ select jsonb '[1]' @? '$[0.5]';
select jsonb '[1]' @? '$[0.9]';
select jsonb '[1]' @? '$[1.2]';
select jsonb '[1]' @? 'strict $[1.2]';
+select jsonb_path_query('[1]', 'strict $[1.2]');
+select jsonb_path_query('{}', 'strict $[0.3]');
+select jsonb '{}' @? 'lax $[0.3]';
+select jsonb_path_query('{}', 'strict $[1.2]');
+select jsonb '{}' @? 'lax $[1.2]';
+select jsonb_path_query('{}', 'strict $[-2 to 3]');
+select jsonb '{}' @? 'lax $[-2 to 3]';
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
@@ -80,6 +88,7 @@ select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10 / 0].a');
select jsonb_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
select jsonb_path_query('1', 'lax $[0]');
select jsonb_path_query('1', 'lax $[*]');
+select jsonb_path_query('{}', 'lax $[0]');
select jsonb_path_query('[1]', 'lax $[0]');
select jsonb_path_query('[1]', 'lax $[*]');
select jsonb_path_query('[1,2,3]', 'lax $[*]');
@@ -90,6 +99,7 @@ select jsonb_path_query('[]', '$[last ? (exists(last))]');
select jsonb_path_query('[]', 'strict $[last]');
select jsonb_path_query('[]', 'strict $[last]', silent => true);
select jsonb_path_query('[1]', '$[last]');
+select jsonb_path_query('{}', '$[last]');
select jsonb_path_query('[1,2,3]', '$[last]');
select jsonb_path_query('[1,2,3]', '$[last - 1]');
select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "number")]');
@@ -137,6 +147,7 @@ select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ >
select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+
select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
@@ -316,8 +327,6 @@ select jsonb_path_query('"nan"', '$.double()');
select jsonb_path_query('"NaN"', '$.double()');
select jsonb_path_query('"inf"', '$.double()');
select jsonb_path_query('"-inf"', '$.double()');
-select jsonb_path_query('"inf"', '$.double()', silent => true);
-select jsonb_path_query('"-inf"', '$.double()', silent => true);
select jsonb_path_query('{}', '$.abs()');
select jsonb_path_query('true', '$.floor()');
@@ -339,11 +348,170 @@ select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "
select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^a b.* c " flag "ix")');
select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "m")');
select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "s")');
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "a\\b" flag "q")');
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "a\\b" flag "")');
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "q")');
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "q")');
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")');
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")');
+
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+select jsonb_path_query('"12:34"', '$.datetime("aaa", 1)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 1)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483647)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483648)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483647)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483648)');
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb_path_query('0', '$.datetime()');
+select jsonb_path_query('0', '$.datetime().type()');
+select jsonb_path_query('1490216035.5', '$.datetime()');
+
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", 12 * 60)');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", -(5 * 3600 + 12 * 60 + 34))');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)',
+ jsonb_build_object('tz', extract(timezone from now())));
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)',
+ jsonb_build_object('tz', extract(timezone from now())));
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
-- jsonpath operators
SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '[$[*].a]');
SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
@@ -351,6 +519,7 @@ SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 1, "max": 4}');
SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 3, "max": 4}');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '[$[*].a]');
SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a', silent => true);
@@ -381,3 +550,75 @@ SELECT jsonb_path_match('[true, true]', '$[*]', silent => false);
SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2';
SELECT jsonb_path_match('[{"a": 1}, {"a": 2}]', '$[*].a > 1');
+
+-- extension: path sequences
+select jsonb_path_query('[1,2,3,4,5]', '10, 20, $[*], 30');
+select jsonb_path_query('[1,2,3,4,5]', 'lax 10, 20, $[*].a, 30');
+select jsonb_path_query('[1,2,3,4,5]', 'strict 10, 20, $[*].a, 30');
+select jsonb_path_query('[1,2,3,4,5]', '-(10, 20, $[1 to 3], 30)');
+select jsonb_path_query('[1,2,3,4,5]', 'lax (10, 20.5, $[1 to 3], "30").double()');
+select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 5) ? (@ == 3)]');
+select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 3) ? (@ == 3)]');
+
+-- extension: array constructors
+select jsonb_path_query('[1, 2, 3]', '[]');
+select jsonb_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5]');
+select jsonb_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5][*]');
+select jsonb_path_query('[1, 2, 3]', '[(1, (2, $[*])), (4, 5)]');
+select jsonb_path_query('[1, 2, 3]', '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]');
+select jsonb_path_query('[1, 2, 3]', 'strict [1, 2, $[*].a, 4, 5]');
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '[$[*][*] ? (@ > 3)]');
+
+-- extension: object constructors
+select jsonb_path_query('[1, 2, 3]', '{}');
+select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}');
+select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}.*');
+select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}[*]');
+select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": ($[*], 4, 5)}');
+select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}');
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+select jsonb '{"a": 1}' @? '$["b"]';
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+
+select jsonb_path_query('{"a": 1}', '$["a"]');
+select jsonb_path_query('{"a": 1}', 'strict $["b"]');
+select jsonb_path_query('{"a": 1}', 'lax $["b"]');
+select jsonb_path_query('{"a": 1, "b": 2}', 'lax $["b", "c", "b", "a", 0 to 3]');
+
+select jsonb_path_query('null', '{"a": 1}["a"]');
+select jsonb_path_query('null', '{"a": 1}["b"]');
+
+-- extension: outer item reference (@N)
+select jsonb_path_query('[2,4,1,5,3]', '$[*] ? (!exists($[*] ? (@ < @1)))');
+select jsonb_path_query('[2,4,1,5,3]', '$[*] ? (!exists($[*] ? (@ > @1)))');
+select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ < @1[1]) > 2)');
+select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ < @1[1]) > 3)');
+select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ ? (@ ? (@ < @3[1]) > @2[0]) > @1[0]) > 2)');
+select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ ? (@ ? (@ < @3[2]) > @2[0]) > @1[0]) > 2)');
+
+-- extension: including subpaths into result
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[*].b)');
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[*]).b');
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.a.([*].b)');
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a)[*].b');
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.a[*].(b)');
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a)[*].(b)');
+select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[0 to 1].b)');
+
+-- extension: user-defined functions and item methods
+-- array_map(jsonpath_fcxt, jsonb) function created in create_function_1.sql
+-- array_map() item method
+select jsonb_path_query('1', 'strict $.array_map(x => x + 10)');
+select jsonb_path_query('1', 'lax $.array_map(x => x + 10)');
+select jsonb_path_query('[1, 2, 3]', '$.array_map(x => x + 10)');
+select jsonb_path_query('[1, 2, 3]', '$.array_map(x => x + 10)[*]');
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.array_map(a => a.array_map(x => x + 10))');
+
+-- array_map() function
+select jsonb_path_query('1', 'strict array_map($, x => x + 10)');
+select jsonb_path_query('1', 'lax array_map($, x => x + 10)');
+select jsonb_path_query('[3, 4, 5]', 'array_map($[*], (x, i) => x + i * 10)');
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'array_map($[*], x => [array_map(x[*], x => x + 10)])');
diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql
new file mode 100644
index 0000000000..0979172e16
--- /dev/null
+++ b/src/test/regress/sql/jsonb_sqljson.sql
@@ -0,0 +1,918 @@
+-- JSON_EXISTS
+
+SELECT JSON_EXISTS(NULL FORMAT JSONB, '$');
+SELECT JSON_EXISTS(NULL::text FORMAT JSONB, '$');
+SELECT JSON_EXISTS(NULL::bytea FORMAT JSONB, '$');
+SELECT JSON_EXISTS(NULL::json FORMAT JSONB, '$');
+SELECT JSON_EXISTS(NULL::jsonb FORMAT JSONB, '$');
+SELECT JSON_EXISTS(NULL::jsonb, '$');
+
+SELECT JSON_EXISTS('' FORMAT JSONB, '$');
+SELECT JSON_EXISTS('' FORMAT JSONB, '$' TRUE ON ERROR);
+SELECT JSON_EXISTS('' FORMAT JSONB, '$' FALSE ON ERROR);
+SELECT JSON_EXISTS('' FORMAT JSONB, '$' UNKNOWN ON ERROR);
+SELECT JSON_EXISTS('' FORMAT JSONB, '$' ERROR ON ERROR);
+
+SELECT JSON_EXISTS(bytea '' FORMAT JSONB, '$' ERROR ON ERROR);
+
+SELECT JSON_EXISTS(jsonb '[]', '$');
+SELECT JSON_EXISTS('[]' FORMAT JSONB, '$');
+SELECT JSON_EXISTS(JSON_OBJECT(RETURNING bytea FORMAT JSONB) FORMAT 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);
+
+-- JSON_VALUE
+
+SELECT JSON_VALUE(NULL FORMAT JSONB, '$');
+SELECT JSON_VALUE(NULL::text FORMAT JSONB, '$');
+SELECT JSON_VALUE(NULL::bytea FORMAT JSONB, '$');
+SELECT JSON_VALUE(NULL::json FORMAT JSONB, '$');
+SELECT JSON_VALUE(NULL::jsonb FORMAT JSONB, '$');
+SELECT JSON_VALUE(NULL::jsonb, '$');
+
+SELECT JSON_VALUE('' FORMAT JSONB, '$');
+SELECT JSON_VALUE('' FORMAT JSONB, '$' NULL ON ERROR);
+SELECT JSON_VALUE('' FORMAT JSONB, '$' DEFAULT '"default value"' ON ERROR);
+SELECT JSON_VALUE('' FORMAT JSONB, '$' ERROR ON ERROR);
+
+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 FORMAT JSONB, '$'),
+ JSON_QUERY(js FORMAT JSONB, '$' WITHOUT WRAPPER),
+ JSON_QUERY(js FORMAT JSONB, '$' WITH CONDITIONAL WRAPPER),
+ JSON_QUERY(js FORMAT JSONB, '$' WITH UNCONDITIONAL ARRAY WRAPPER),
+ JSON_QUERY(js FORMAT JSONB, '$' WITH ARRAY WRAPPER)
+FROM
+ (VALUES
+ ('null'),
+ ('12.3'),
+ ('true'),
+ ('"aaa"'),
+ ('[1, null, "2"]'),
+ ('{"a": 1, "b": [2]}')
+ ) foo(js);
+
+SELECT
+ JSON_QUERY(js FORMAT JSONB, 'strict $[*]') AS "unspec",
+ JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITHOUT WRAPPER) AS "without",
+ JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond",
+ JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond",
+ JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITH ARRAY WRAPPER) AS "with"
+FROM
+ (VALUES
+ ('1'),
+ ('[]'),
+ ('[null]'),
+ ('[12.3]'),
+ ('[true]'),
+ ('["aaa"]'),
+ ('[[1, 2, 3]]'),
+ ('[{"a": 1, "b": [2]}]'),
+ ('[1, "2", null, [3]]')
+ ) foo(js);
+
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text);
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text KEEP QUOTES);
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text KEEP QUOTES ON SCALAR STRING);
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text OMIT QUOTES);
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text OMIT QUOTES ON SCALAR STRING);
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' OMIT QUOTES ERROR ON ERROR);
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING json OMIT QUOTES ERROR ON ERROR);
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR);
+SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING bytea FORMAT JSONB 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('[]' FORMAT JSONB, '$[*]');
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' NULL ON EMPTY);
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' EMPTY ARRAY ON EMPTY);
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' EMPTY OBJECT ON EMPTY);
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY);
+
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY NULL ON ERROR);
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR);
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR);
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY ERROR ON ERROR);
+SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON ERROR);
+
+SELECT JSON_QUERY('[1,2]' FORMAT JSONB, '$[*]' ERROR 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 text FORMAT JSONB);
+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 FORMAT JSONB);
+
+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 bytea FORMAT JSONB 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 FORMAT 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 FORMAT JSONB, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]')
+ CONSTRAINT test_jsonb_constraint5
+ CHECK (JSON_QUERY(js FORMAT JSONB, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a')
+);
+
+\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;
+
+-- JSON_TABLE
+
+-- Should fail (JSON_TABLE can be used only in FROM clause)
+SELECT JSON_TABLE('[]', '$');
+
+-- Should fail (no columns)
+SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ());
+
+-- NULL => empty table
+SELECT * FROM JSON_TABLE(NULL FORMAT JSONB, '$' COLUMNS (foo int)) bar;
+
+-- invalid json => empty table
+SELECT * FROM JSON_TABLE('' FORMAT JSONB, '$' COLUMNS (foo int)) bar;
+
+-- invalid json => error
+SELECT * FROM JSON_TABLE('' FORMAT JSONB, '$' COLUMNS (foo int) ERROR ON ERROR) bar;
+
+--
+SELECT * FROM JSON_TABLE('123' FORMAT JSONB, '$'
+ COLUMNS (item int PATH '$', foo int)) bar;
+
+SELECT * FROM JSON_TABLE(jsonb '123', '$'
+ COLUMNS (item int PATH '$', foo int)) bar;
+
+-- JSON_TABLE: basic functionality
+SELECT *
+FROM
+ (VALUES
+ ('1'),
+ ('[]'),
+ ('{}'),
+ ('[1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]'),
+ ('err')
+ ) vals(js)
+ LEFT OUTER JOIN
+-- JSON_TABLE is implicitly lateral
+ JSON_TABLE(
+ vals.js FORMAT JSONB, 'lax $[*]'
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY, -- allowed additional ordinality columns
+ "int" int PATH '$',
+ "text" text PATH '$',
+ "char(4)" char(4) PATH '$',
+ "bool" bool PATH '$',
+ "numeric" numeric PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc char(4) FORMAT JSON PATH '$',
+ jsv varchar(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSONB PATH '$',
+ aaa int, -- implicit path '$."aaa"',
+ aaa1 int PATH '$.aaa'
+ )
+ ) jt
+ ON true;
+
+-- JSON_TABLE: Test backward parsing
+
+CREATE VIEW jsonb_table_view AS
+SELECT * FROM
+ JSON_TABLE(
+ 'null' FORMAT JSONB, 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ id2 FOR ORDINALITY, -- allowed additional ordinality columns
+ "int" int PATH '$',
+ "text" text PATH '$',
+ "char(4)" char(4) PATH '$',
+ "bool" bool PATH '$',
+ "numeric" numeric PATH '$',
+ js json PATH '$',
+ jb jsonb PATH '$',
+ jst text FORMAT JSON PATH '$',
+ jsc char(4) FORMAT JSON PATH '$',
+ jsv varchar(4) FORMAT JSON PATH '$',
+ jsb jsonb FORMAT JSONB PATH '$',
+ aaa int, -- implicit path '$."aaa"',
+ aaa1 int PATH '$.aaa',
+ NESTED PATH '$[1]' AS p1 COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' AS "p1 1" COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' AS p2 COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1" COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' AS p22 COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
+
+\sv jsonb_table_view
+
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
+
+-- JSON_TABLE: ON EMPTY/ON ERROR behavior
+SELECT *
+FROM
+ (VALUES ('1'), ('err'), ('"err"')) vals(js),
+ JSON_TABLE(vals.js FORMAT JSONB, '$' COLUMNS (a int PATH '$')) jt;
+
+SELECT *
+FROM
+ (VALUES ('1'), ('err'), ('"err"')) vals(js)
+ LEFT OUTER JOIN
+ JSON_TABLE(vals.js FORMAT JSONB, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt
+ ON true;
+
+SELECT *
+FROM
+ (VALUES ('1'), ('err'), ('"err"')) vals(js)
+ LEFT OUTER JOIN
+ JSON_TABLE(vals.js FORMAT JSONB, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
+ ON true;
+
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+
+-- JSON_TABLE: nested paths and plans
+
+-- Should fail (JSON_TABLE columns shall contain explicit AS path
+-- specifications if explicit PLAN clause is used)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' -- AS required here
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS ( -- AS required here
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+
+-- Should fail (column names anf path names shall be distinct)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS a
+ COLUMNS (
+ a int
+ )
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS a
+ COLUMNS (
+ b int,
+ NESTED PATH '$' AS a
+ COLUMNS (
+ c int
+ )
+ )
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$'
+ COLUMNS (
+ b int,
+ NESTED PATH '$' AS b
+ COLUMNS (
+ c int
+ )
+ )
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$'
+ COLUMNS (
+ NESTED PATH '$' AS a
+ COLUMNS (
+ b int
+ ),
+ NESTED PATH '$'
+ COLUMNS (
+ NESTED PATH '$' AS a
+ COLUMNS (
+ c int
+ )
+ )
+ )
+) jt;
+
+-- JSON_TABLE: plan validation
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p1)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER p3)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 UNION p1 UNION p11)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p13))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER p11) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' -- without root path name
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))
+) jt;
+
+-- JSON_TABLE: plan execution
+
+CREATE TEMP TABLE jsonb_table_test (js text);
+
+INSERT INTO jsonb_table_test
+VALUES (
+ '[
+ {"a": 1, "b": [], "c": []},
+ {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]},
+ {"a": 3, "b": [1, 2], "c": []},
+ {"x": "4", "b": [1, 2], "c": 123}
+ ]'
+);
+
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ ) jt;
+
+-- default plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, union)
+ ) jt;
+
+-- specific plan (p outer (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb union pc))
+ ) jt;
+
+-- specific plan (p outer (pc union pb))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pc union pb))
+ ) jt;
+
+-- default plan (inner, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner)
+ ) jt;
+
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb union pc))
+ ) jt;
+
+-- default plan (inner, cross)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (cross, inner)
+ ) jt;
+
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb cross pc))
+ ) jt;
+
+-- default plan (outer, cross)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt;
+
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js FORMAT JSONB,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb cross pc))
+ ) jt;
+
+
+select
+ jt.*, b1 + 100 as b
+from
+ json_table (jsonb
+ '[
+ {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]},
+ {"a": 2, "b": [10, 20], "c": [1, null, 2]},
+ {"x": "3", "b": [11, 22, 33, 44]}
+ ]',
+ '$[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on error,
+ nested path 'strict $.b[*]' as pb columns (
+ b text format json path '$',
+ nested path 'strict $[*]' as pb1 columns (
+ b1 int path '$'
+ )
+ ),
+ nested path 'strict $.c[*]' as pc columns (
+ c text format json path '$',
+ nested path 'strict $[*]' as pc1 columns (
+ c1 int path '$'
+ )
+ )
+ )
+ --plan default(outer, cross)
+ plan(p outer ((pb inner pb1) cross (pc outer pc1)))
+ ) jt;
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(jsonb
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >= $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ jsonb '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+
+-- Extension: non-constant JSON path
+SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a');
+SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a');
+SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY);
+SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a');
+SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
+-- Should fail (invalid path)
+SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error');
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
+
+-- Test parallel JSON_VALUE()
+CREATE TABLE test_parallel_jsonb_value AS
+SELECT i::text::jsonb AS js
+FROM generate_series(1, 1000000) i;
+
+-- Should be non-parallel due to subtransactions
+EXPLAIN (COSTS OFF)
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
+
+-- Should be parallel
+EXPLAIN (COSTS OFF)
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value;
+SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value;
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 9171ddbc6c..16818f40ca 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -34,6 +34,7 @@ select '''\b\f\r\n\t\v\"\''\\'''::jsonpath;
select '"\x50\u0067\u{53}\u{051}\u{00004C}"'::jsonpath;
select '''\x50\u0067\u{53}\u{051}\u{00004C}'''::jsonpath;
select '$.foo\x50\u0067\u{53}\u{051}\u{00004C}\t\"bar'::jsonpath;
+select '$."foo\x50\u0067\u{53}\u{051}\u{00004C}\t\"bar"'::jsonpath;
select '$.g ? ($.a == 1)'::jsonpath;
select '$.g ? (@ == 1)'::jsonpath;
@@ -72,6 +73,9 @@ select '"aaa".type()'::jsonpath;
select 'true.type()'::jsonpath;
select '$.double().floor().ceiling().abs()'::jsonpath;
select '$.keyvalue().key'::jsonpath;
+select '$.datetime()'::jsonpath;
+select '$.datetime("datetime template")'::jsonpath;
+select '$.datetime("datetime template", "default timezone")'::jsonpath;
select '$ ? (@ starts with "abc")'::jsonpath;
select '$ ? (@ starts with $var)'::jsonpath;
@@ -83,6 +87,9 @@ select '$ ? (@ like_regex "pattern" flag "i")'::jsonpath;
select '$ ? (@ like_regex "pattern" flag "is")'::jsonpath;
select '$ ? (@ like_regex "pattern" flag "isim")'::jsonpath;
select '$ ? (@ like_regex "pattern" flag "xsms")'::jsonpath;
+select '$ ? (@ like_regex "pattern" flag "q")'::jsonpath;
+select '$ ? (@ like_regex "pattern" flag "iq")'::jsonpath;
+select '$ ? (@ like_regex "pattern" flag "smixq")'::jsonpath;
select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath;
select '$ < 1'::jsonpath;
@@ -99,6 +106,20 @@ select '($)'::jsonpath;
select '(($))'::jsonpath;
select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+select '(1, 2, $.a)'::jsonpath;
+select '(1, 2, $.a).a[*]'::jsonpath;
+select '(1, 2, $.a) == 5'::jsonpath;
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+
+select '[]'::jsonpath;
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+
+select '{}'::jsonpath;
+select '{a: 1 + 2}'::jsonpath;
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+
select '$ ? (@.a < 1)'::jsonpath;
select '$ ? (@.a < -1)'::jsonpath;
select '$ ? (@.a < +1)'::jsonpath;
@@ -150,6 +171,8 @@ select '$ ? (@.a < +10.1e+1)'::jsonpath;
select '0'::jsonpath;
select '00'::jsonpath;
+select '$.00'::jsonpath;
+select '$.0a'::jsonpath;
select '0.0'::jsonpath;
select '0.000'::jsonpath;
select '0.000e1'::jsonpath;
@@ -175,3 +198,81 @@ select '1..e'::jsonpath;
select '1..e3'::jsonpath;
select '(1.).e'::jsonpath;
select '(1.).e3'::jsonpath;
+
+select '@1'::jsonpath;
+select '@-1'::jsonpath;
+select '$ ? (@0 > 1)'::jsonpath;
+select '$ ? (@1 > 1)'::jsonpath;
+select '$.a ? (@.b ? (@1 > @) > 5)'::jsonpath;
+select '$.a ? (@.b ? (@2 > @) > 5)'::jsonpath;
+
+-- jsonpath combination operators
+
+select jsonpath '$.a' == jsonpath '$[*] + 1';
+-- should fail
+select jsonpath '$.a' == jsonpath '$.b == 1';
+--select jsonpath '$.a' != jsonpath '$[*] + 1';
+select jsonpath '$.a' > jsonpath '$[*] + 1';
+select jsonpath '$.a' < jsonpath '$[*] + 1';
+select jsonpath '$.a' >= jsonpath '$[*] + 1';
+select jsonpath '$.a' <= jsonpath '$[*] + 1';
+select jsonpath '$.a' + jsonpath '$[*] + 1';
+select jsonpath '$.a' - jsonpath '$[*] + 1';
+select jsonpath '$.a' * jsonpath '$[*] + 1';
+select jsonpath '$.a' / jsonpath '$[*] + 1';
+select jsonpath '$.a' % jsonpath '$[*] + 1';
+
+select jsonpath '$.a' == jsonb '"aaa"';
+--select jsonpath '$.a' != jsonb '1';
+select jsonpath '$.a' > jsonb '12.34';
+select jsonpath '$.a' < jsonb '"aaa"';
+select jsonpath '$.a' >= jsonb 'true';
+select jsonpath '$.a' <= jsonb 'false';
+select jsonpath '$.a' + jsonb 'null';
+select jsonpath '$.a' - jsonb '12.3';
+select jsonpath '$.a' * jsonb '5';
+select jsonpath '$.a' / jsonb '0';
+select jsonpath '$.a' % jsonb '"1.23"';
+select jsonpath '$.a' == jsonb '[]';
+select jsonpath '$.a' >= jsonb '[1, "2", true, null, [], {"a": [1], "b": 3}]';
+select jsonpath '$.a' + jsonb '{}';
+select jsonpath '$.a' / jsonb '{"a": 1, "b": [1, {}], "c": {}, "d": {"e": true, "f": {"g": "abc"}}}';
+
+
+select jsonpath '$' -> 'a';
+select jsonpath '$' -> 1;
+select jsonpath '$' -> 'a' -> 1;
+select jsonpath '$.a' ? jsonpath '$.x ? (@.y ? (@ > 3 + @1.b + $) == $) > $.z';
+
+select jsonpath '$.a.b[($[*]?(@ > @0).c + 1.23).**{2 to 5}] ? ({a: @, b: [$.x, [], @ % 5]}.b[2] > 3)' ?
+ jsonpath '$.**[$.size() + 3] ? (@ + $ ? (@ > @1 ? ($1 + $2 * @ - $ != 5) / $) < 10) > true';
+
+select jsonpath '$.a + $a' @ jsonb '"aaa"';
+select jsonpath '$.a + $a' @ jsonb '{"b": "abc"}';
+select jsonpath '$.a + $a' @ jsonb '{"a": "abc"}';
+select jsonpath '$.a + $a.double()' @ jsonb '{"a": "abc"}';
+select jsonpath '$.a + $a.x.double()' @ jsonb '{"a": {"x": -12.34}}';
+select jsonpath '$[*] ? (@ > $min && @ <= $max)' @ jsonb '{"min": -1.23, "max": 5.0}';
+select jsonpath '$[*] ? (@ > $min && @ <= $max)' @ jsonb '{"min": -1.23}' @ jsonb '{"max": 5.0}';
+
+-- extension: user-defined item methods and functions with lambda expressions
+select jsonpath 'foo()';
+select jsonpath '$.foo()';
+select jsonpath 'foo($[*])';
+select jsonpath '$.foo("bar")';
+select jsonpath 'foo($[*], "bar")';
+select jsonpath '$.foo("bar", 123 + 456, "baz".type())';
+select jsonpath 'foo($[*], "bar", 123 + 456, "baz".type())';
+select jsonpath '$.foo(() => 1)';
+select jsonpath 'foo($[*], () => 1)';
+select jsonpath '$.foo((x) => x + 5)';
+select jsonpath 'foo($[*], (x) => x + 5)';
+select jsonpath '$.foo(x => x + 5)';
+select jsonpath 'foo($[*], x => x + 5)';
+select jsonpath '$.foo((x, y) => x + 5 * y)';
+select jsonpath 'foo($[*], (x, y) => x + 5 * y)';
+select jsonpath '$.foo((x, y) => x + 5 * y, z => z.type(), () => $.bar(x => x + 1)).baz()';
+select jsonpath 'foo($[*], (x, y) => x + 5 * y, z => z.type(), () => $.bar(x => x + 1)).baz()';
+-- should fail
+select jsonpath '$.foo((x, y.a) => x + 5)';
+select jsonpath '$.foo((x, y + 1, z) => x + y)';
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 1227ef79f0..6cf75410d4 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -485,6 +485,10 @@ WHERE c.castfunc = p.oid AND
-- As of 9.1, this finds the cast from pg_node_tree to text, which we
-- intentionally do not provide a reverse pathway for.
+-- As of 10.0, this finds the cast from jsonb to bytea, because those are
+-- binary-compatible while the reverse goes through jsonb_from_bytea(),
+-- which does a jsonb structure validation.
+
SELECT castsource::regtype, casttarget::regtype, castfunc, castcontext
FROM pg_cast c
WHERE c.castmethod = 'b' AND
@@ -874,8 +878,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..b24911adf5
--- /dev/null
+++ b/src/test/regress/sql/sqljson.sql
@@ -0,0 +1,417 @@
+-- JSON_OBJECT()
+SELECT JSON_OBJECT();
+SELECT JSON_OBJECT(RETURNING json);
+SELECT JSON_OBJECT(RETURNING json FORMAT JSON);
+SELECT JSON_OBJECT(RETURNING json FORMAT JSONB);
+SELECT JSON_OBJECT(RETURNING jsonb);
+SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSON);
+SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSONB);
+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 text FORMAT JSONB);
+SELECT JSON_OBJECT(RETURNING text FORMAT JSONB ENCODING UTF8);
+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(RETURNING bytea FORMAT JSONB);
+
+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 text) FORMAT JSONB);
+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': JSON_OBJECT('b': 1 RETURNING bytea FORMAT JSONB));
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea FORMAT JSONB) FORMAT JSONB);
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea FORMAT JSON) FORMAT JSONB);
+
+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 json FORMAT JSONB);
+SELECT JSON_ARRAY(RETURNING jsonb);
+SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON);
+SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSONB);
+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 text FORMAT JSONB);
+SELECT JSON_ARRAY(RETURNING text FORMAT JSONB ENCODING UTF8);
+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(RETURNING bytea FORMAT JSONB);
+
+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(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSONB);
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING bytea FORMAT JSONB));
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING bytea FORMAT JSONB) FORMAT JSONB);
+
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i));
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i));
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) RETURNING jsonb);
+--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL);
+--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL RETURNING jsonb);
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (3), (1), (NULL), (2)) foo(i) ORDER BY i);
+-- Should fail
+SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i));
+SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i));
+SELECT JSON_ARRAY(SELECT * FROM (VALUES (1, 2)) foo(i, j));
+
+-- JSON_ARRAYAGG()
+SELECT JSON_ARRAYAGG(i) IS NULL,
+ JSON_ARRAYAGG(i RETURNING jsonb) IS NULL
+FROM generate_series(1, 0) i;
+
+SELECT JSON_ARRAYAGG(i),
+ JSON_ARRAYAGG(i RETURNING jsonb)
+FROM generate_series(1, 5) i;
+
+SELECT JSON_ARRAYAGG(i ORDER BY i DESC)
+FROM generate_series(1, 5) i;
+
+SELECT JSON_ARRAYAGG(i::text::json)
+FROM generate_series(1, 5) i;
+
+SELECT JSON_ARRAYAGG(JSON_ARRAY(i, i + 1 RETURNING text) FORMAT JSON)
+FROM generate_series(1, 5) i;
+
+SELECT JSON_ARRAYAGG(NULL),
+ JSON_ARRAYAGG(NULL RETURNING jsonb)
+FROM generate_series(1, 5);
+
+SELECT JSON_ARRAYAGG(NULL NULL ON NULL),
+ JSON_ARRAYAGG(NULL NULL ON NULL RETURNING jsonb)
+FROM generate_series(1, 5);
+
+SELECT
+ JSON_ARRAYAGG(bar),
+ JSON_ARRAYAGG(bar RETURNING jsonb),
+ JSON_ARRAYAGG(bar ABSENT ON NULL),
+ JSON_ARRAYAGG(bar ABSENT ON NULL RETURNING jsonb),
+ JSON_ARRAYAGG(bar NULL ON NULL),
+ JSON_ARRAYAGG(bar NULL ON NULL RETURNING jsonb),
+ JSON_ARRAYAGG(foo),
+ JSON_ARRAYAGG(foo RETURNING jsonb),
+ JSON_ARRAYAGG(foo ORDER BY bar) FILTER (WHERE bar > 2),
+ JSON_ARRAYAGG(foo ORDER BY bar RETURNING jsonb) FILTER (WHERE bar > 2)
+FROM
+ (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL)) foo(bar);
+
+SELECT
+ bar, JSON_ARRAYAGG(bar) FILTER (WHERE bar > 2) OVER (PARTITION BY foo.bar % 2)
+FROM
+ (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL), (5), (4)) foo(bar);
+
+-- JSON_OBJECTAGG()
+SELECT JSON_OBJECTAGG('key': 1) IS NULL,
+ JSON_OBJECTAGG('key': 1 RETURNING jsonb) IS NULL
+WHERE FALSE;
+
+SELECT JSON_OBJECTAGG(NULL: 1);
+
+SELECT JSON_OBJECTAGG(NULL: 1 RETURNING jsonb);
+
+SELECT
+ JSON_OBJECTAGG(i: i),
+-- JSON_OBJECTAGG(i VALUE i),
+-- JSON_OBJECTAGG(KEY i VALUE i),
+ JSON_OBJECTAGG(i: i RETURNING jsonb)
+FROM
+ generate_series(1, 5) i;
+
+SELECT
+ JSON_OBJECTAGG(k: v),
+ JSON_OBJECTAGG(k: v NULL ON NULL),
+ JSON_OBJECTAGG(k: v ABSENT ON NULL),
+ JSON_OBJECTAGG(k: v RETURNING jsonb),
+ JSON_OBJECTAGG(k: v NULL ON NULL RETURNING jsonb),
+ JSON_OBJECTAGG(k: v ABSENT ON NULL RETURNING jsonb)
+FROM
+ (VALUES (1, 1), (1, NULL), (2, NULL), (3, 3)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (0, NULL), (3, NULL), (2, 2), (4, NULL)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS RETURNING jsonb)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+
+-- Test JSON_OBJECT deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);
+
+CREATE VIEW json_object_view AS
+SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);
+
+\sv json_object_view
+
+DROP VIEW json_object_view;
+
+-- Test JSON_ARRAY deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json);
+
+CREATE VIEW json_array_view AS
+SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json);
+
+\sv json_array_view
+
+DROP VIEW json_array_view;
+
+-- Test JSON_OBJECTAGG deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) OVER (PARTITION BY i % 2)
+FROM generate_series(1,5) i;
+
+CREATE VIEW json_objectagg_view AS
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+
+\sv json_objectagg_view
+
+DROP VIEW json_objectagg_view;
+
+-- Test JSON_ARRAYAGG deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (PARTITION BY i % 2)
+FROM generate_series(1,5) i;
+
+CREATE VIEW json_arrayagg_view AS
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+
+\sv json_arrayagg_view
+
+DROP VIEW json_arrayagg_view;
+
+-- Test JSON_ARRAY(subquery) deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb);
+
+CREATE VIEW json_array_subquery_view AS
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb);
+
+\sv json_array_subquery_view
+
+DROP VIEW json_array_subquery_view;
+
+-- IS JSON predicate
+SELECT NULL IS JSON;
+SELECT NULL IS NOT JSON;
+SELECT NULL::json IS JSON;
+SELECT NULL::jsonb IS JSON;
+SELECT NULL::text IS JSON;
+SELECT NULL::bytea IS JSON;
+SELECT NULL::int IS JSON;
+
+SELECT '' IS JSON;
+SELECT '' FORMAT JSON IS JSON;
+SELECT '' FORMAT JSONB IS JSON;
+
+SELECT bytea '\x00' IS JSON;
+SELECT bytea '\x00' FORMAT JSON IS JSON;
+SELECT bytea '\x00' FORMAT JSONB 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",
+ js FORMAT JSON IS JSON "FORMAT JSON IS JSON"
+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",
+ js FORMAT JSON IS JSON "FORMAT JSON IS JSON"
+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",
+ js FORMAT JSON IS JSON "FORMAT JSON IS JSON",
+ js FORMAT JSONB IS JSON "FORMAT JSONB IS JSON"
+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",
+ js FORMAT JSON IS JSON "FORMAT JSON IS JSON"
+FROM
+ (SELECT js::jsonb FROM test_is_json WHERE js IS JSON) foo(js);
+
+SELECT
+ js0,
+ js FORMAT JSONB IS JSON "IS JSON",
+ js FORMAT JSONB IS NOT JSON "IS NOT JSON",
+ js FORMAT JSONB IS JSON VALUE "IS VALUE",
+ js FORMAT JSONB IS JSON OBJECT "IS OBJECT",
+ js FORMAT JSONB IS JSON ARRAY "IS ARRAY",
+ js FORMAT JSONB IS JSON SCALAR "IS SCALAR",
+ js FORMAT JSONB IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js FORMAT JSONB IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js, js::jsonb::bytea FROM test_is_json WHERE js IS JSON) foo(js0, 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;
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index 150eb54c87..dcc5ff61f3 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -228,5 +228,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMP_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c3bd46c233..588c3e033f 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -252,6 +252,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMPTZ_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bdcbc8d15e..41bcb6090a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1121,6 +1121,9 @@ JsValue
JsonAggState
JsonBaseObjectInfo
JsonHashEntry
+JsonItem
+JsonItemStack
+JsonItemStackEntry
JsonIterateStringValuesAction
JsonLexContext
JsonLikeRegexContext
@@ -1142,7 +1145,11 @@ JsonPathKeyword
JsonPathParseItem
JsonPathParseResult
JsonPathPredicateCallback
+<<<<<<< HEAD
JsonPathString
+=======
+JsonPathUserFuncContext
+>>>>>>> 19911df... Extract common code from jsonb_path_*() functions
JsonSemAction
JsonTokenType
JsonTransformStringValuesAction