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