From 1f4fe35512dafbba89e6fe89606c5639f10e69b1 Mon Sep 17 00:00:00 2001 From: Naisila Puka <37271756+naisila@users.noreply.github.com> Date: Wed, 24 Aug 2022 19:11:18 +0300 Subject: [PATCH] Support JSON_TABLE on PG 15 (#6241) Postgres supports JSON_TABLE feature on PG 15. We treat JSON_TABLE the same as correlated functions (e.g., recurring tuples). In the end, for multi-shard JSON_TABLE commands, we apply the same restrictions as reference tables (e.g., cannot be in the outer part of an outer join etc.) Co-authored-by: Onder Kalaci --- .../planner/multi_logical_planner.c | 3 +- .../planner/query_pushdown_planning.c | 63 +- .../distributed/query_pushdown_planning.h | 2 +- .../expected/json_table_select_only.out | 1583 +++++++++++++++++ .../expected/json_table_select_only_0.out | 9 + src/test/regress/expected/pg15_json.out | 487 +++++ src/test/regress/expected/pg15_json_0.out | 9 + src/test/regress/json_table_select_only.out | 1572 ++++++++++++++++ src/test/regress/json_table_select_only_0.out | 9 + src/test/regress/multi_schedule | 2 +- .../regress/sql/json_table_select_only.sql | 330 ++++ src/test/regress/sql/pg15_json.sql | 325 ++++ 12 files changed, 4387 insertions(+), 7 deletions(-) create mode 100644 src/test/regress/expected/json_table_select_only.out create mode 100644 src/test/regress/expected/json_table_select_only_0.out create mode 100644 src/test/regress/expected/pg15_json.out create mode 100644 src/test/regress/expected/pg15_json_0.out create mode 100644 src/test/regress/json_table_select_only.out create mode 100644 src/test/regress/json_table_select_only_0.out create mode 100644 src/test/regress/sql/json_table_select_only.sql create mode 100644 src/test/regress/sql/pg15_json.sql diff --git a/src/backend/distributed/planner/multi_logical_planner.c b/src/backend/distributed/planner/multi_logical_planner.c index 7e665b567..14dfa924f 100644 --- a/src/backend/distributed/planner/multi_logical_planner.c +++ b/src/backend/distributed/planner/multi_logical_planner.c @@ -1154,7 +1154,8 @@ HasComplexRangeTableType(Query *queryTree) if (rangeTableEntry->rtekind != RTE_RELATION && rangeTableEntry->rtekind != RTE_SUBQUERY && rangeTableEntry->rtekind != RTE_FUNCTION && - rangeTableEntry->rtekind != RTE_VALUES) + rangeTableEntry->rtekind != RTE_VALUES && + !IsJsonTableRTE(rangeTableEntry)) { hasComplexRangeTableType = true; } diff --git a/src/backend/distributed/planner/query_pushdown_planning.c b/src/backend/distributed/planner/query_pushdown_planning.c index 5ad7887e9..964acfff4 100644 --- a/src/backend/distributed/planner/query_pushdown_planning.c +++ b/src/backend/distributed/planner/query_pushdown_planning.c @@ -60,7 +60,8 @@ typedef enum RecurringTuplesType RECURRING_TUPLES_FUNCTION, RECURRING_TUPLES_EMPTY_JOIN_TREE, RECURRING_TUPLES_RESULT_FUNCTION, - RECURRING_TUPLES_VALUES + RECURRING_TUPLES_VALUES, + RECURRING_TUPLES_JSON_TABLE } RecurringTuplesType; /* @@ -345,7 +346,8 @@ IsFunctionOrValuesRTE(Node *node) RangeTblEntry *rangeTblEntry = (RangeTblEntry *) node; if (rangeTblEntry->rtekind == RTE_FUNCTION || - rangeTblEntry->rtekind == RTE_VALUES) + rangeTblEntry->rtekind == RTE_VALUES || + IsJsonTableRTE(rangeTblEntry)) { return true; } @@ -718,6 +720,13 @@ DeferErrorIfFromClauseRecurs(Query *queryTree) "the FROM clause contains VALUES", NULL, NULL); } + else if (recurType == RECURRING_TUPLES_JSON_TABLE) + { + return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "correlated subqueries are not supported when " + "the FROM clause contains JSON_TABLE", NULL, + NULL); + } /* @@ -945,6 +954,13 @@ DeferredErrorIfUnsupportedRecurringTuplesJoin( "There exist a VALUES clause in the outer " "part of the outer join", NULL); } + else if (recurType == RECURRING_TUPLES_JSON_TABLE) + { + return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "cannot pushdown the subquery", + "There exist a JSON_TABLE clause in the outer " + "part of the outer join", NULL); + } return NULL; } @@ -1235,7 +1251,8 @@ DeferErrorIfUnsupportedTableCombination(Query *queryTree) */ if (rangeTableEntry->rtekind == RTE_RELATION || rangeTableEntry->rtekind == RTE_SUBQUERY || - rangeTableEntry->rtekind == RTE_RESULT) + rangeTableEntry->rtekind == RTE_RESULT || + IsJsonTableRTE(rangeTableEntry)) /* TODO: can we have volatile???*/ { /* accepted */ } @@ -1403,6 +1420,13 @@ DeferErrorIfUnsupportedUnionQuery(Query *subqueryTree) "VALUES is not supported within a " "UNION", NULL); } + else if (recurType == RECURRING_TUPLES_JSON_TABLE) + { + return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "cannot push down this subquery", + "JSON_TABLE is not supported within a " + "UNION", NULL); + } return NULL; } @@ -1502,6 +1526,11 @@ RecurringTypeDescription(RecurringTuplesType recurType) return "a VALUES clause"; } + case RECURRING_TUPLES_JSON_TABLE: + { + return "a JSON_TABLE"; + } + case RECURRING_TUPLES_INVALID: { /* @@ -1698,7 +1727,8 @@ DeferredErrorIfUnsupportedLateralSubquery(PlannerInfo *plannerInfo, * strings anyway. */ if (recurType != RECURRING_TUPLES_VALUES && - recurType != RECURRING_TUPLES_RESULT_FUNCTION) + recurType != RECURRING_TUPLES_RESULT_FUNCTION && + recurType != RECURRING_TUPLES_JSON_TABLE) { recurTypeDescription = psprintf("%s (%s)", recurTypeDescription, recurringRangeTableEntry->eref-> @@ -1775,6 +1805,26 @@ ContainsRecurringRangeTable(List *rangeTable, RecurringTuplesType *recurType) } +/* + * IsJsonTableRTE checks whether the RTE refers to a JSON_TABLE + * table function, which was introduced in PostgreSQL 15. + */ +bool +IsJsonTableRTE(RangeTblEntry *rte) +{ +#if PG_VERSION_NUM >= PG_VERSION_15 + if (rte == NULL) + { + return false; + } + return (rte->rtekind == RTE_TABLEFUNC && + rte->tablefunc->functype == TFT_JSON_TABLE); +#endif + + return false; +} + + /* * HasRecurringTuples returns whether any part of the expression will generate * the same set of tuples in every query on shards when executing a distributed @@ -1836,6 +1886,11 @@ HasRecurringTuples(Node *node, RecurringTuplesType *recurType) *recurType = RECURRING_TUPLES_VALUES; return true; } + else if (IsJsonTableRTE(rangeTableEntry)) + { + *recurType = RECURRING_TUPLES_JSON_TABLE; + return true; + } return false; } diff --git a/src/include/distributed/query_pushdown_planning.h b/src/include/distributed/query_pushdown_planning.h index 061a4a730..3c30b7814 100644 --- a/src/include/distributed/query_pushdown_planning.h +++ b/src/include/distributed/query_pushdown_planning.h @@ -46,6 +46,6 @@ extern DeferredErrorMessage * DeferErrorIfCannotPushdownSubquery(Query *subquery bool outerMostQueryHasLimit); extern DeferredErrorMessage * DeferErrorIfUnsupportedUnionQuery(Query *queryTree); - +extern bool IsJsonTableRTE(RangeTblEntry *rte); #endif /* QUERY_PUSHDOWN_PLANNING_H */ diff --git a/src/test/regress/expected/json_table_select_only.out b/src/test/regress/expected/json_table_select_only.out new file mode 100644 index 000000000..0ce4edc68 --- /dev/null +++ b/src/test/regress/expected/json_table_select_only.out @@ -0,0 +1,1583 @@ +-- +-- PG15+ test +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q +\endif +SET search_path TO "json table"; +CREATE SCHEMA "json table"; +SET search_path TO "json table"; +CREATE TABLE jsonb_table_test (id bigserial, js jsonb); +SELECT create_distributed_table('jsonb_table_test', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- insert some data +INSERT INTO jsonb_table_test (js) +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} + ]' +), +( + '[ + {"a": 1, "d": [], "c": []}, + {"a": 2, "d": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "d": [1, 2], "c": []}, + {"x": "4", "d": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [100, 200, 300], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": [null]}, + {"x": "4", "b": [1, 2], "c": 2} + ]' +), +( + '[ + {"y": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "t": [1, 2], "c": []}, + {"x": "4", "b": [1, 200], "c": 96} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "100", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"t": 1, "b": [], "c": []}, + {"t": 2, "b": [1, 2, 3], "x": [10, null, 20]}, + {"t": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"U": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"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} + ]' +), +( + '[ + {"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} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1000, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "T": [1, 2], "c": 123} + ]' +), +( + '[ + {"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} + ]' +), +( + '[ + {"ffa": 1, "b": [], "c": []}, + {"ffb": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"fffc": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +); +-- unspecified plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' 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 ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- default plan (outer, union) +select + jt.* +from + jsonb_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 (outer, union) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- specific plan (p outer (pb union pc)) +select + jt.* +from + jsonb_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 (p outer (pb union pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- specific plan (p outer (pc union pb)) +select + jt.* +from + jsonb_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 (p outer (pc union pb)) + ) jt ORDER BY 1,2,3,4; + n | a | c | b +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 10 | + 2 | -1 | 20 | + 2 | -1 | | 1 + 2 | -1 | | 1 + 2 | -1 | | 2 + 2 | -1 | | 2 + 2 | -1 | | 3 + 2 | -1 | | 3 + 2 | -1 | | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 100 + 2 | 2 | | 200 + 2 | 2 | | 300 + 2 | 2 | | 1000 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | | 1 + 3 | -1 | | 1 + 3 | -1 | | 2 + 3 | -1 | | 2 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 200 + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- default plan (inner, union) +select + jt.* +from + jsonb_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 ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | +(107 rows) + +-- specific plan (p inner (pb union pc)) +select + jt.* +from + jsonb_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 (p inner (pb union pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | +(107 rows) + +-- default plan (inner, cross) +select + jt.* +from + jsonb_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 (cross, inner) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 3 | 3 | 1 | + 3 | 3 | 2 | +(92 rows) + +-- specific plan (p inner (pb cross pc)) +select + jt.* +from + jsonb_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 (p inner (pb cross pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 3 | 3 | 1 | + 3 | 3 | 2 | +(92 rows) + +-- default plan (outer, cross) +select + jt.* +from + jsonb_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 (outer, cross) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | -1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 2 | 2 | | + 3 | -1 | | + 3 | -1 | | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | +(129 rows) + +-- specific plan (p outer (pb cross pc)) +select + jt.* +from + jsonb_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 (p outer (pb cross pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | -1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 2 | 2 | | + 3 | -1 | | + 3 | -1 | | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | +(129 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 ORDER BY 1,2,3,4,5; + n | a | b | b1 | c | c1 | b +--------------------------------------------------------------------- + 1 | 1 | [1, 10] | 1 | 1 | | 101 + 1 | 1 | [1, 10] | 1 | 2 | | 101 + 1 | 1 | [1, 10] | 1 | null | | 101 + 1 | 1 | [1, 10] | 10 | 1 | | 110 + 1 | 1 | [1, 10] | 10 | 2 | | 110 + 1 | 1 | [1, 10] | 10 | null | | 110 + 1 | 1 | [2] | 2 | 1 | | 102 + 1 | 1 | [2] | 2 | 2 | | 102 + 1 | 1 | [2] | 2 | null | | 102 + 1 | 1 | [3, 30, 300] | 3 | 1 | | 103 + 1 | 1 | [3, 30, 300] | 3 | 2 | | 103 + 1 | 1 | [3, 30, 300] | 3 | null | | 103 + 1 | 1 | [3, 30, 300] | 30 | 1 | | 130 + 1 | 1 | [3, 30, 300] | 30 | 2 | | 130 + 1 | 1 | [3, 30, 300] | 30 | null | | 130 + 1 | 1 | [3, 30, 300] | 300 | 1 | | 400 + 1 | 1 | [3, 30, 300] | 300 | 2 | | 400 + 1 | 1 | [3, 30, 300] | 300 | null | | 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 ORDER BY 4,1,2,3; + x | y | y | z +--------------------------------------------------------------------- + 2 | 1 | [1, 2, 3] | 1 + 3 | 1 | [1, 2, 3] | 1 + 4 | 1 | [1, 2, 3] | 1 + 2 | 1 | [1, 2, 3] | 2 + 2 | 2 | [1, 2, 3] | 2 + 3 | 1 | [1, 2, 3] | 2 + 3 | 1 | [2, 3, 4, 5] | 2 + 3 | 2 | [1, 2, 3] | 2 + 3 | 2 | [2, 3, 4, 5] | 2 + 4 | 1 | [1, 2, 3] | 2 + 4 | 1 | [2, 3, 4, 5] | 2 + 4 | 2 | [1, 2, 3] | 2 + 4 | 2 | [2, 3, 4, 5] | 2 + 2 | 1 | [1, 2, 3] | 3 + 2 | 2 | [1, 2, 3] | 3 + 2 | 3 | [1, 2, 3] | 3 + 3 | 1 | [1, 2, 3] | 3 + 3 | 1 | [2, 3, 4, 5] | 3 + 3 | 2 | [1, 2, 3] | 3 + 3 | 2 | [2, 3, 4, 5] | 3 + 3 | 3 | [1, 2, 3] | 3 + 3 | 3 | [2, 3, 4, 5] | 3 + 4 | 1 | [1, 2, 3] | 3 + 4 | 1 | [2, 3, 4, 5] | 3 + 4 | 1 | [3, 4, 5, 6] | 3 + 4 | 2 | [1, 2, 3] | 3 + 4 | 2 | [2, 3, 4, 5] | 3 + 4 | 2 | [3, 4, 5, 6] | 3 + 4 | 3 | [1, 2, 3] | 3 + 4 | 3 | [2, 3, 4, 5] | 3 + 4 | 3 | [3, 4, 5, 6] | 3 + 3 | 1 | [2, 3, 4, 5] | 4 + 3 | 2 | [2, 3, 4, 5] | 4 + 3 | 3 | [2, 3, 4, 5] | 4 + 4 | 1 | [2, 3, 4, 5] | 4 + 4 | 1 | [3, 4, 5, 6] | 4 + 4 | 2 | [2, 3, 4, 5] | 4 + 4 | 2 | [3, 4, 5, 6] | 4 + 4 | 3 | [2, 3, 4, 5] | 4 + 4 | 3 | [3, 4, 5, 6] | 4 + 3 | 1 | [2, 3, 4, 5] | 5 + 3 | 2 | [2, 3, 4, 5] | 5 + 3 | 3 | [2, 3, 4, 5] | 5 + 4 | 1 | [2, 3, 4, 5] | 5 + 4 | 1 | [3, 4, 5, 6] | 5 + 4 | 2 | [2, 3, 4, 5] | 5 + 4 | 2 | [3, 4, 5, 6] | 5 + 4 | 3 | [2, 3, 4, 5] | 5 + 4 | 3 | [3, 4, 5, 6] | 5 + 4 | 1 | [3, 4, 5, 6] | 6 + 4 | 2 | [3, 4, 5, 6] | 6 + 4 | 3 | [3, 4, 5, 6] | 6 +(52 rows) + +SET client_min_messages TO ERROR; +DROP SCHEMA "json table" CASCADE; diff --git a/src/test/regress/expected/json_table_select_only_0.out b/src/test/regress/expected/json_table_select_only_0.out new file mode 100644 index 000000000..c04e76814 --- /dev/null +++ b/src/test/regress/expected/json_table_select_only_0.out @@ -0,0 +1,9 @@ +-- +-- PG15+ test +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q diff --git a/src/test/regress/expected/pg15_json.out b/src/test/regress/expected/pg15_json.out new file mode 100644 index 000000000..6e12fc453 --- /dev/null +++ b/src/test/regress/expected/pg15_json.out @@ -0,0 +1,487 @@ +-- +-- PG15+ test +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q +\endif +CREATE SCHEMA pg15_json; +SET search_path TO pg15_json; +SET citus.next_shard_id TO 1687000; +CREATE TABLE test_table(id bigserial, value text); +SELECT create_distributed_table('test_table', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO test_table (value) SELECT i::text FROM generate_series(0,100)i; +CREATE TABLE my_films(id bigserial, js jsonb); +SELECT create_distributed_table('my_films', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO my_films(js) VALUES ( +'{ "favorites" : [ + { "kind" : "comedy", "films" : [ { "title" : "Bananas", "director" : "Woody Allen"}, + { "title" : "The Dinner Game", "director" : "Francis Veber" } ] }, + { "kind" : "horror", "films" : [{ "title" : "Psycho", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "thriller", "films" : [{ "title" : "Vertigo", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "drama", "films" : [{ "title" : "Yojimbo", "director" : "Akira Kurosawa" } ] } + ] }'); +INSERT INTO my_films(js) VALUES ( +'{ "favorites" : [ + { "kind" : "comedy", "films" : [ { "title" : "Bananas2", "director" : "Woody Allen"}, + { "title" : "The Dinner Game2", "director" : "Francis Veber" } ] }, + { "kind" : "horror", "films" : [{ "title" : "Psycho2", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "thriller", "films" : [{ "title" : "Vertigo2", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "drama", "films" : [{ "title" : "Yojimbo2", "director" : "Akira Kurosawa" } ] } + ] }'); +-- a router query +SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt + WHERE my_films.id = 1 + ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa +(5 rows) + +-- router query with an explicit LATEREL SUBQUERY +SELECT sub.* +FROM my_films, + lateral(SELECT * FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt) as sub +WHERE my_films.id = 1; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa +(5 rows) + +-- router query with an explicit LATEREL SUBQUERY and LIMIT +SELECT sub.* +FROM my_films, + lateral(SELECT * FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt ORDER BY id DESC LIMIT 1) as sub +WHERE my_films.id = 1; + id | kind | title | director +--------------------------------------------------------------------- + 4 | drama | Yojimbo | Akira Kurosawa +(1 row) + +-- set it DEBUG1 in case the plan changes +-- we can see details +SET client_min_messages TO DEBUG1; +-- a mult-shard query +SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt + ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | Bananas2 | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 1 | comedy | The Dinner Game2 | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 2 | horror | Psycho2 | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 3 | thriller | Vertigo2 | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa + 4 | drama | Yojimbo2 | Akira Kurosawa +(10 rows) + +-- recursively plan subqueries that has JSON_TABLE +SELECT count(*) FROM +( + SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt + LIMIT 1) as sub_with_json, test_table +WHERE test_table.id = sub_with_json.id; +DEBUG: push down of limit count: 1 +DEBUG: generating subplan XXX_1 for subquery SELECT jt.id, jt.kind, jt.title, jt.director FROM pg15_json.my_films, LATERAL JSON_TABLE(my_films.js, '$."favorites"[*]' AS json_table_path_1 COLUMNS (id FOR ORDINALITY, kind text PATH '$."kind"', NESTED PATH '$."films"[*]' AS json_table_path_2 COLUMNS (title text PATH '$."title"', director text PATH '$."director"')) PLAN (json_table_path_1 OUTER json_table_path_2)) jt LIMIT 1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.id, intermediate_result.kind, intermediate_result.title, intermediate_result.director FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer, kind text, title text, director text)) sub_with_json, pg15_json.test_table WHERE (test_table.id OPERATOR(pg_catalog.=) sub_with_json.id) + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- multi-shard query with an explicit LATEREL SUBQUERY +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) + ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | Bananas2 | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 1 | comedy | The Dinner Game2 | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 2 | horror | Psycho2 | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 3 | thriller | Vertigo2 | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa + 4 | drama | Yojimbo2 | Akira Kurosawa +(10 rows) + +-- JSON_TABLE can be on the inner part of an outer joion +SELECT sub.* +FROM my_films LEFT JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) + ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | Bananas2 | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 1 | comedy | The Dinner Game2 | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 2 | horror | Psycho2 | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 3 | thriller | Vertigo2 | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa + 4 | drama | Yojimbo2 | Akira Kurosawa +(10 rows) + +-- we can pushdown this correlated subquery in WHERE clause +SELECT count(*) +FROM my_films WHERE + (SELECT count(*) > 0 + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000); + count +--------------------------------------------------------------------- + 2 +(1 row) + +-- we can pushdown this correlated subquery in SELECT clause + SELECT (SELECT count(*) > 0 + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt) +FROM my_films; + ?column? +--------------------------------------------------------------------- + t + t +(2 rows) + +-- multi-shard query with an explicit LATEREL SUBQUERY +-- along with other tables +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) JOIN test_table ON(my_films.id = test_table.id) + ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | Bananas2 | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 1 | comedy | The Dinner Game2 | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 2 | horror | Psycho2 | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 3 | thriller | Vertigo2 | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa + 4 | drama | Yojimbo2 | Akira Kurosawa +(10 rows) + +-- non-colocated join fails +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) JOIN test_table ON(my_films.id != test_table.id) + ORDER BY 1,2,3,4; +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +-- JSON_TABLE can be in the outer part of the join +-- as long as there is a distributed table +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) LEFT JOIN test_table ON(my_films.id = test_table.id) + ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | Bananas2 | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 1 | comedy | The Dinner Game2 | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 2 | horror | Psycho2 | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 3 | thriller | Vertigo2 | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa + 4 | drama | Yojimbo2 | Akira Kurosawa +(10 rows) + +-- JSON_TABLE cannot be on the outer side of the join +SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY, column_a int4 PATH '$.a', column_b int4 PATH '$.b', a int4, b int4, c text)) +LEFT JOIN LATERAL + (SELECT * + FROM my_films) AS foo on(foo.id = a); +ERROR: cannot pushdown the subquery +DETAIL: There exist a JSON_TABLE clause in the outer part of the outer join +-- JSON_TABLE cannot be on the FROM clause alone +SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY, column_a int4 PATH '$.a', column_b int4 PATH '$.b', a int4, b int4, c text)) as foo +WHERE b > + (SELECT count(*) + FROM my_films WHERE id = foo.a); +ERROR: correlated subqueries are not supported when the FROM clause contains JSON_TABLE +-- we can recursively plan json_tables on set operations +(SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY)) ORDER BY id ASC LIMIT 1) +UNION +(SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY)) ORDER BY id ASC LIMIT 1) +UNION +(SELECT id FROM test_table ORDER BY id ASC LIMIT 1); +DEBUG: generating subplan XXX_1 for subquery SELECT id FROM JSON_TABLE('[{"a": 10, "b": 20}, {"a": 30, "b": 40}]'::jsonb, '$[*]' AS json_table_path_1 COLUMNS (id FOR ORDINALITY) PLAN (json_table_path_1)) ORDER BY id LIMIT 1 +DEBUG: generating subplan XXX_2 for subquery SELECT id FROM JSON_TABLE('[{"a": 10, "b": 20}, {"a": 30, "b": 40}]'::jsonb, '$[*]' AS json_table_path_1 COLUMNS (id FOR ORDINALITY) PLAN (json_table_path_1)) ORDER BY id LIMIT 1 +DEBUG: push down of limit count: 1 +DEBUG: generating subplan XXX_3 for subquery SELECT id FROM pg15_json.test_table ORDER BY id LIMIT 1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer) UNION SELECT intermediate_result.id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(id integer) UNION SELECT intermediate_result.id FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(id bigint) + id +--------------------------------------------------------------------- + 1 +(1 row) + +-- LIMIT in subquery not supported when json_table exists +SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY, column_a int4 PATH '$.a', column_b int4 PATH '$.b', a int4, b int4, c text)) +JOIN LATERAL + (SELECT * + FROM my_films WHERE json_table.id = a LIMIT 1) as foo ON (true); +ERROR: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a lateral subquery references a column from a JSON_TABLE +-- a little more complex query with multiple json_table +SELECT + director1 AS director, title1, kind1, title2, kind2 +FROM + my_films, + JSON_TABLE ( js, '$.favorites' AS favs COLUMNS ( + NESTED PATH '$[*]' AS films1 COLUMNS ( + kind1 text PATH '$.kind', + NESTED PATH '$.films[*]' AS film1 COLUMNS ( + title1 text PATH '$.title', + director1 text PATH '$.director') + ), + NESTED PATH '$[*]' AS films2 COLUMNS ( + kind2 text PATH '$.kind', + NESTED PATH '$.films[*]' AS film2 COLUMNS ( + title2 text PATH '$.title', + director2 text PATH '$.director' + ) + ) + ) + PLAN (favs INNER ((films1 INNER film1) CROSS (films2 INNER film2))) + ) AS jt + WHERE kind1 > kind2 AND director1 = director2; + director | title1 | kind1 | title2 | kind2 +--------------------------------------------------------------------- + Alfred Hitchcock | Vertigo | thriller | Psycho | horror + Alfred Hitchcock | Vertigo2 | thriller | Psycho2 | horror +(2 rows) + +RESET client_min_messages; +-- test some utility functions on the target list & where clause +select jsonb_path_exists(js, '$.favorites') from my_films; + jsonb_path_exists +--------------------------------------------------------------------- + t + t +(2 rows) + +select bool_and(JSON_EXISTS(js, '$.favorites.films.title')) from my_films; + bool_and +--------------------------------------------------------------------- + t +(1 row) + +SELECT count(*) FROM my_films WHERE jsonb_path_exists(js, '$.favorites'); + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT count(*) FROM my_films WHERE jsonb_path_exists(js, '$.favorites'); + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT count(*) FROM my_films WHERE JSON_EXISTS(js, '$.favorites.films.title'); + count +--------------------------------------------------------------------- + 2 +(1 row) + +-- check constraint with json_exists +create table user_profiles ( + id bigserial, + addresses jsonb, + anyjson jsonb, + check (json_exists( addresses, '$.main' )) +); +select create_distributed_table('user_profiles', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +insert into user_profiles (addresses) VALUES (JSON_SCALAR('1')); +ERROR: new row for relation "user_profiles_1687008" violates check constraint "user_profiles_addresses_check" +DETAIL: Failing row contains (1, "1", null). +CONTEXT: while executing command on localhost:xxxxx +insert into user_profiles (addresses) VALUES ('{"main":"value"}'); +-- we cannot insert because WITH UNIQUE KEYS +insert into user_profiles (addresses) VALUES (JSON ('{"main":"value", "main":"value"}' WITH UNIQUE KEYS)); +ERROR: duplicate JSON object key value +-- we can insert with +insert into user_profiles (addresses) VALUES (JSON ('{"main":"value", "main":"value"}' WITHOUT UNIQUE KEYS)) RETURNING *; + id | addresses | anyjson +--------------------------------------------------------------------- + 4 | {"main": "value"} | +(1 row) + +TRUNCATE user_profiles; +INSERT INTO user_profiles (anyjson) VALUES ('12'), ('"abc"'), ('[1,2,3]'), ('{"a":12}'); +select anyjson, anyjson is json array as json_array, anyjson is json object as json_object, anyjson is json scalar as json_scalar, +anyjson is json with UNIQUE keys +from user_profiles WHERE anyjson IS NOT NULL ORDER BY 1; + anyjson | json_array | json_object | json_scalar | ?column? +--------------------------------------------------------------------- + "abc" | f | f | t | t + 12 | f | f | t | t + [1, 2, 3] | t | f | f | t + {"a": 12} | f | t | f | t +(4 rows) + +-- use json_query +SELECT i, + json_query('[{"x": "aaa"},{"x": "bbb"},{"x": "ccc"}]'::JSONB, '$[$i].x' passing id AS i RETURNING text omit quotes) +FROM generate_series(0, 3) i +JOIN my_films ON(id = i); + i | json_query +--------------------------------------------------------------------- + 1 | bbb + 2 | ccc +(2 rows) + +-- we can use JSON_TABLE in modification queries as well +-- use log level such that we can see trace changes +SET client_min_messages TO DEBUG1; +--the JSON_TABLE subquery is recursively planned +UPDATE test_table SET VALUE = 'XXX' FROM( +SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt) as foo WHERE foo.id = test_table.id; +DEBUG: generating subplan XXX_1 for subquery SELECT jt.id, jt.kind, jt.title, jt.director FROM pg15_json.my_films, LATERAL JSON_TABLE(my_films.js, '$."favorites"[*]' AS json_table_path_1 COLUMNS (id FOR ORDINALITY, kind text PATH '$."kind"', NESTED PATH '$."films"[*]' AS json_table_path_2 COLUMNS (title text PATH '$."title"', director text PATH '$."director"')) PLAN (json_table_path_1 OUTER json_table_path_2)) jt +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE pg15_json.test_table SET value = 'XXX'::text FROM (SELECT intermediate_result.id, intermediate_result.kind, intermediate_result.title, intermediate_result.director FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer, kind text, title text, director text)) foo WHERE (foo.id OPERATOR(pg_catalog.=) test_table.id) +-- Subquery with JSON table can be pushed down because two distributed tables +-- in the query are joined on distribution column +UPDATE test_table SET VALUE = 'XXX' FROM ( +SELECT my_films.id, jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt) as foo WHERE foo.id = test_table.id; +-- we can pushdown with CTEs as well +WITH json_cte AS +(SELECT my_films.id, jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt) +UPDATE test_table SET VALUE = 'XYZ' FROM json_cte + WHERE json_cte.id = test_table.id; + -- we can recursively with CTEs as well +WITH json_cte AS +(SELECT my_films.id as film_id, jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + id FOR ORDINALITY, + title text PATH '$.title', + director text PATH '$.director'))) AS jt ORDER BY jt.id LIMIT 1) +UPDATE test_table SET VALUE = 'XYZ' FROM json_cte + WHERE json_cte.film_id = test_table.id; +DEBUG: generating subplan XXX_1 for CTE json_cte: SELECT my_films.id AS film_id, jt.kind, jt.id, jt.title, jt.director FROM pg15_json.my_films, LATERAL JSON_TABLE(my_films.js, '$."favorites"[*]' AS json_table_path_1 COLUMNS (kind text PATH '$."kind"', NESTED PATH '$."films"[*]' AS json_table_path_2 COLUMNS (id FOR ORDINALITY, title text PATH '$."title"', director text PATH '$."director"')) PLAN (json_table_path_1 OUTER json_table_path_2)) jt ORDER BY jt.id LIMIT 1 +DEBUG: push down of limit count: 1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE pg15_json.test_table SET value = 'XYZ'::text FROM (SELECT intermediate_result.film_id, intermediate_result.kind, intermediate_result.id, intermediate_result.title, intermediate_result.director FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(film_id bigint, kind text, id integer, title text, director text)) json_cte WHERE (json_cte.film_id OPERATOR(pg_catalog.=) test_table.id) +SET client_min_messages TO ERROR; +DROP SCHEMA pg15_json CASCADE; diff --git a/src/test/regress/expected/pg15_json_0.out b/src/test/regress/expected/pg15_json_0.out new file mode 100644 index 000000000..c04e76814 --- /dev/null +++ b/src/test/regress/expected/pg15_json_0.out @@ -0,0 +1,9 @@ +-- +-- PG15+ test +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q diff --git a/src/test/regress/json_table_select_only.out b/src/test/regress/json_table_select_only.out new file mode 100644 index 000000000..61a120202 --- /dev/null +++ b/src/test/regress/json_table_select_only.out @@ -0,0 +1,1572 @@ +-- +-- PG15+ test +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q +\endif +SET search_path TO "json table"; +-- insert some data +INSERT INTO jsonb_table_test (js) +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} + ]' +), +( + '[ + {"a": 1, "d": [], "c": []}, + {"a": 2, "d": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "d": [1, 2], "c": []}, + {"x": "4", "d": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [100, 200, 300], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": [null]}, + {"x": "4", "b": [1, 2], "c": 2} + ]' +), +( + '[ + {"y": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "t": [1, 2], "c": []}, + {"x": "4", "b": [1, 200], "c": 96} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "100", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"t": 1, "b": [], "c": []}, + {"t": 2, "b": [1, 2, 3], "x": [10, null, 20]}, + {"t": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"U": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"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} + ]' +), +( + '[ + {"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} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1000, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "T": [1, 2], "c": 123} + ]' +), +( + '[ + {"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} + ]' +), +( + '[ + {"ffa": 1, "b": [], "c": []}, + {"ffb": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"fffc": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +); +-- unspecified plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' 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 ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- default plan (outer, union) +select + jt.* +from + jsonb_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 (outer, union) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- specific plan (p outer (pb union pc)) +select + jt.* +from + jsonb_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 (p outer (pb union pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- specific plan (p outer (pc union pb)) +select + jt.* +from + jsonb_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 (p outer (pc union pb)) + ) jt ORDER BY 1,2,3,4; + n | a | c | b +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 10 | + 2 | -1 | 20 | + 2 | -1 | | 1 + 2 | -1 | | 1 + 2 | -1 | | 2 + 2 | -1 | | 2 + 2 | -1 | | 3 + 2 | -1 | | 3 + 2 | -1 | | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 100 + 2 | 2 | | 200 + 2 | 2 | | 300 + 2 | 2 | | 1000 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | | 1 + 3 | -1 | | 1 + 3 | -1 | | 2 + 3 | -1 | | 2 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 200 + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- default plan (inner, union) +select + jt.* +from + jsonb_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 ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | +(107 rows) + +-- specific plan (p inner (pb union pc)) +select + jt.* +from + jsonb_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 (p inner (pb union pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | +(107 rows) + +-- default plan (inner, cross) +select + jt.* +from + jsonb_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 (cross, inner) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 3 | 3 | 1 | + 3 | 3 | 2 | +(92 rows) + +-- specific plan (p inner (pb cross pc)) +select + jt.* +from + jsonb_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 (p inner (pb cross pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 3 | 3 | 1 | + 3 | 3 | 2 | +(92 rows) + +-- default plan (outer, cross) +select + jt.* +from + jsonb_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 (outer, cross) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | -1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 2 | 2 | | + 3 | -1 | | + 3 | -1 | | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | +(129 rows) + +-- specific plan (p outer (pb cross pc)) +select + jt.* +from + jsonb_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 (p outer (pb cross pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | -1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 2 | 2 | | + 3 | -1 | | + 3 | -1 | | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | +(129 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 ORDER BY 1,2,3,4,5; + n | a | b | b1 | c | c1 | b +--------------------------------------------------------------------- + 1 | 1 | [1, 10] | 1 | 1 | | 101 + 1 | 1 | [1, 10] | 1 | 2 | | 101 + 1 | 1 | [1, 10] | 1 | null | | 101 + 1 | 1 | [1, 10] | 10 | 1 | | 110 + 1 | 1 | [1, 10] | 10 | 2 | | 110 + 1 | 1 | [1, 10] | 10 | null | | 110 + 1 | 1 | [2] | 2 | 1 | | 102 + 1 | 1 | [2] | 2 | 2 | | 102 + 1 | 1 | [2] | 2 | null | | 102 + 1 | 1 | [3, 30, 300] | 3 | 1 | | 103 + 1 | 1 | [3, 30, 300] | 3 | 2 | | 103 + 1 | 1 | [3, 30, 300] | 3 | null | | 103 + 1 | 1 | [3, 30, 300] | 30 | 1 | | 130 + 1 | 1 | [3, 30, 300] | 30 | 2 | | 130 + 1 | 1 | [3, 30, 300] | 30 | null | | 130 + 1 | 1 | [3, 30, 300] | 300 | 1 | | 400 + 1 | 1 | [3, 30, 300] | 300 | 2 | | 400 + 1 | 1 | [3, 30, 300] | 300 | null | | 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 ORDER BY 4,1,2,3; + x | y | y | z +--------------------------------------------------------------------- + 2 | 1 | [1, 2, 3] | 1 + 3 | 1 | [1, 2, 3] | 1 + 4 | 1 | [1, 2, 3] | 1 + 2 | 1 | [1, 2, 3] | 2 + 2 | 2 | [1, 2, 3] | 2 + 3 | 1 | [1, 2, 3] | 2 + 3 | 1 | [2, 3, 4, 5] | 2 + 3 | 2 | [1, 2, 3] | 2 + 3 | 2 | [2, 3, 4, 5] | 2 + 4 | 1 | [1, 2, 3] | 2 + 4 | 1 | [2, 3, 4, 5] | 2 + 4 | 2 | [1, 2, 3] | 2 + 4 | 2 | [2, 3, 4, 5] | 2 + 2 | 1 | [1, 2, 3] | 3 + 2 | 2 | [1, 2, 3] | 3 + 2 | 3 | [1, 2, 3] | 3 + 3 | 1 | [1, 2, 3] | 3 + 3 | 1 | [2, 3, 4, 5] | 3 + 3 | 2 | [1, 2, 3] | 3 + 3 | 2 | [2, 3, 4, 5] | 3 + 3 | 3 | [1, 2, 3] | 3 + 3 | 3 | [2, 3, 4, 5] | 3 + 4 | 1 | [1, 2, 3] | 3 + 4 | 1 | [2, 3, 4, 5] | 3 + 4 | 1 | [3, 4, 5, 6] | 3 + 4 | 2 | [1, 2, 3] | 3 + 4 | 2 | [2, 3, 4, 5] | 3 + 4 | 2 | [3, 4, 5, 6] | 3 + 4 | 3 | [1, 2, 3] | 3 + 4 | 3 | [2, 3, 4, 5] | 3 + 4 | 3 | [3, 4, 5, 6] | 3 + 3 | 1 | [2, 3, 4, 5] | 4 + 3 | 2 | [2, 3, 4, 5] | 4 + 3 | 3 | [2, 3, 4, 5] | 4 + 4 | 1 | [2, 3, 4, 5] | 4 + 4 | 1 | [3, 4, 5, 6] | 4 + 4 | 2 | [2, 3, 4, 5] | 4 + 4 | 2 | [3, 4, 5, 6] | 4 + 4 | 3 | [2, 3, 4, 5] | 4 + 4 | 3 | [3, 4, 5, 6] | 4 + 3 | 1 | [2, 3, 4, 5] | 5 + 3 | 2 | [2, 3, 4, 5] | 5 + 3 | 3 | [2, 3, 4, 5] | 5 + 4 | 1 | [2, 3, 4, 5] | 5 + 4 | 1 | [3, 4, 5, 6] | 5 + 4 | 2 | [2, 3, 4, 5] | 5 + 4 | 2 | [3, 4, 5, 6] | 5 + 4 | 3 | [2, 3, 4, 5] | 5 + 4 | 3 | [3, 4, 5, 6] | 5 + 4 | 1 | [3, 4, 5, 6] | 6 + 4 | 2 | [3, 4, 5, 6] | 6 + 4 | 3 | [3, 4, 5, 6] | 6 +(52 rows) + diff --git a/src/test/regress/json_table_select_only_0.out b/src/test/regress/json_table_select_only_0.out new file mode 100644 index 000000000..c04e76814 --- /dev/null +++ b/src/test/regress/json_table_select_only_0.out @@ -0,0 +1,9 @@ +-- +-- PG15+ test +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q diff --git a/src/test/regress/multi_schedule b/src/test/regress/multi_schedule index f43a1ec9a..a92da42fd 100644 --- a/src/test/regress/multi_schedule +++ b/src/test/regress/multi_schedule @@ -55,7 +55,7 @@ test: subquery_in_targetlist subquery_in_where subquery_complex_target_list subq test: subquery_prepared_statements test: non_colocated_leaf_subquery_joins non_colocated_subquery_joins test: cte_inline recursive_view_local_table values sequences_with_different_types -test: pg13 pg12 +test: pg13 pg12 pg15_json json_table_select_only # run pg14 sequentially as it syncs metadata test: pg14 test: pg15 diff --git a/src/test/regress/sql/json_table_select_only.sql b/src/test/regress/sql/json_table_select_only.sql new file mode 100644 index 000000000..250315a25 --- /dev/null +++ b/src/test/regress/sql/json_table_select_only.sql @@ -0,0 +1,330 @@ +-- +-- PG15+ test +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q +\endif + +SET search_path TO "json table"; + +CREATE SCHEMA "json table"; +SET search_path TO "json table"; +CREATE TABLE jsonb_table_test (id bigserial, js jsonb); +SELECT create_distributed_table('jsonb_table_test', 'id'); + +-- insert some data +INSERT INTO jsonb_table_test (js) +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} + ]' +), +( + '[ + {"a": 1, "d": [], "c": []}, + {"a": 2, "d": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "d": [1, 2], "c": []}, + {"x": "4", "d": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [100, 200, 300], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": [null]}, + {"x": "4", "b": [1, 2], "c": 2} + ]' +), +( + '[ + {"y": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "t": [1, 2], "c": []}, + {"x": "4", "b": [1, 200], "c": 96} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "100", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"t": 1, "b": [], "c": []}, + {"t": 2, "b": [1, 2, 3], "x": [10, null, 20]}, + {"t": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"U": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"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} + ]' +), +( + '[ + {"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} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1000, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "T": [1, 2], "c": 123} + ]' +), +( + '[ + {"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} + ]' +), +( + '[ + {"ffa": 1, "b": [], "c": []}, + {"ffb": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"fffc": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +); + +-- unspecified plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' 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 ORDER BY 1,2,3,4; + + + +-- default plan (outer, union) +select + jt.* +from + jsonb_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 (outer, union) + ) jt ORDER BY 1,2,3,4; + +-- specific plan (p outer (pb union pc)) +select + jt.* +from + jsonb_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 (p outer (pb union pc)) + ) jt ORDER BY 1,2,3,4; + +-- specific plan (p outer (pc union pb)) +select + jt.* +from + jsonb_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 (p outer (pc union pb)) + ) jt ORDER BY 1,2,3,4; + +-- default plan (inner, union) +select + jt.* +from + jsonb_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 ORDER BY 1,2,3,4; + +-- specific plan (p inner (pb union pc)) +select + jt.* +from + jsonb_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 (p inner (pb union pc)) + ) jt ORDER BY 1,2,3,4; + +-- default plan (inner, cross) +select + jt.* +from + jsonb_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 (cross, inner) + ) jt ORDER BY 1,2,3,4; + +-- specific plan (p inner (pb cross pc)) +select + jt.* +from + jsonb_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 (p inner (pb cross pc)) + ) jt ORDER BY 1,2,3,4; + +-- default plan (outer, cross) +select + jt.* +from + jsonb_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 (outer, cross) + ) jt ORDER BY 1,2,3,4; + +-- specific plan (p outer (pb cross pc)) +select + jt.* +from + jsonb_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 (p outer (pb cross pc)) + ) jt ORDER BY 1,2,3,4; + + +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 ORDER BY 1,2,3,4,5; + +-- 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 ORDER BY 4,1,2,3; + +SET client_min_messages TO ERROR; +DROP SCHEMA "json table" CASCADE; + diff --git a/src/test/regress/sql/pg15_json.sql b/src/test/regress/sql/pg15_json.sql new file mode 100644 index 000000000..0bc2b177f --- /dev/null +++ b/src/test/regress/sql/pg15_json.sql @@ -0,0 +1,325 @@ +-- +-- PG15+ test +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q +\endif + +CREATE SCHEMA pg15_json; +SET search_path TO pg15_json; + +SET citus.next_shard_id TO 1687000; + +CREATE TABLE test_table(id bigserial, value text); +SELECT create_distributed_table('test_table', 'id'); +INSERT INTO test_table (value) SELECT i::text FROM generate_series(0,100)i; + + +CREATE TABLE my_films(id bigserial, js jsonb); +SELECT create_distributed_table('my_films', 'id'); + +INSERT INTO my_films(js) VALUES ( +'{ "favorites" : [ + { "kind" : "comedy", "films" : [ { "title" : "Bananas", "director" : "Woody Allen"}, + { "title" : "The Dinner Game", "director" : "Francis Veber" } ] }, + { "kind" : "horror", "films" : [{ "title" : "Psycho", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "thriller", "films" : [{ "title" : "Vertigo", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "drama", "films" : [{ "title" : "Yojimbo", "director" : "Akira Kurosawa" } ] } + ] }'); + +INSERT INTO my_films(js) VALUES ( +'{ "favorites" : [ + { "kind" : "comedy", "films" : [ { "title" : "Bananas2", "director" : "Woody Allen"}, + { "title" : "The Dinner Game2", "director" : "Francis Veber" } ] }, + { "kind" : "horror", "films" : [{ "title" : "Psycho2", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "thriller", "films" : [{ "title" : "Vertigo2", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "drama", "films" : [{ "title" : "Yojimbo2", "director" : "Akira Kurosawa" } ] } + ] }'); + +-- a router query +SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt + WHERE my_films.id = 1 + ORDER BY 1,2,3,4; + +-- router query with an explicit LATEREL SUBQUERY +SELECT sub.* +FROM my_films, + lateral(SELECT * FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt) as sub +WHERE my_films.id = 1; + +-- router query with an explicit LATEREL SUBQUERY and LIMIT +SELECT sub.* +FROM my_films, + lateral(SELECT * FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt ORDER BY id DESC LIMIT 1) as sub +WHERE my_films.id = 1; + +-- set it DEBUG1 in case the plan changes +-- we can see details +SET client_min_messages TO DEBUG1; + +-- a mult-shard query +SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt + ORDER BY 1,2,3,4; + +-- recursively plan subqueries that has JSON_TABLE +SELECT count(*) FROM +( + SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt + LIMIT 1) as sub_with_json, test_table +WHERE test_table.id = sub_with_json.id; + + +-- multi-shard query with an explicit LATEREL SUBQUERY +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) + ORDER BY 1,2,3,4; + +-- JSON_TABLE can be on the inner part of an outer joion +SELECT sub.* +FROM my_films LEFT JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) + ORDER BY 1,2,3,4; + +-- we can pushdown this correlated subquery in WHERE clause +SELECT count(*) +FROM my_films WHERE + (SELECT count(*) > 0 + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000); + +-- we can pushdown this correlated subquery in SELECT clause + SELECT (SELECT count(*) > 0 + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt) +FROM my_films; + +-- multi-shard query with an explicit LATEREL SUBQUERY +-- along with other tables +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) JOIN test_table ON(my_films.id = test_table.id) + ORDER BY 1,2,3,4; + +-- non-colocated join fails +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) JOIN test_table ON(my_films.id != test_table.id) + ORDER BY 1,2,3,4; + +-- JSON_TABLE can be in the outer part of the join +-- as long as there is a distributed table +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) LEFT JOIN test_table ON(my_films.id = test_table.id) + ORDER BY 1,2,3,4; + +-- JSON_TABLE cannot be on the outer side of the join +SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY, column_a int4 PATH '$.a', column_b int4 PATH '$.b', a int4, b int4, c text)) +LEFT JOIN LATERAL + (SELECT * + FROM my_films) AS foo on(foo.id = a); + + +-- JSON_TABLE cannot be on the FROM clause alone +SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY, column_a int4 PATH '$.a', column_b int4 PATH '$.b', a int4, b int4, c text)) as foo +WHERE b > + (SELECT count(*) + FROM my_films WHERE id = foo.a); + +-- we can recursively plan json_tables on set operations +(SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY)) ORDER BY id ASC LIMIT 1) +UNION +(SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY)) ORDER BY id ASC LIMIT 1) +UNION +(SELECT id FROM test_table ORDER BY id ASC LIMIT 1); + +-- LIMIT in subquery not supported when json_table exists +SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY, column_a int4 PATH '$.a', column_b int4 PATH '$.b', a int4, b int4, c text)) +JOIN LATERAL + (SELECT * + FROM my_films WHERE json_table.id = a LIMIT 1) as foo ON (true); + +-- a little more complex query with multiple json_table +SELECT + director1 AS director, title1, kind1, title2, kind2 +FROM + my_films, + JSON_TABLE ( js, '$.favorites' AS favs COLUMNS ( + NESTED PATH '$[*]' AS films1 COLUMNS ( + kind1 text PATH '$.kind', + NESTED PATH '$.films[*]' AS film1 COLUMNS ( + title1 text PATH '$.title', + director1 text PATH '$.director') + ), + NESTED PATH '$[*]' AS films2 COLUMNS ( + kind2 text PATH '$.kind', + NESTED PATH '$.films[*]' AS film2 COLUMNS ( + title2 text PATH '$.title', + director2 text PATH '$.director' + ) + ) + ) + PLAN (favs INNER ((films1 INNER film1) CROSS (films2 INNER film2))) + ) AS jt + WHERE kind1 > kind2 AND director1 = director2; + +RESET client_min_messages; + +-- test some utility functions on the target list & where clause +select jsonb_path_exists(js, '$.favorites') from my_films; +select bool_and(JSON_EXISTS(js, '$.favorites.films.title')) from my_films; +SELECT count(*) FROM my_films WHERE jsonb_path_exists(js, '$.favorites'); +SELECT count(*) FROM my_films WHERE jsonb_path_exists(js, '$.favorites'); +SELECT count(*) FROM my_films WHERE JSON_EXISTS(js, '$.favorites.films.title'); + +-- check constraint with json_exists +create table user_profiles ( + id bigserial, + addresses jsonb, + anyjson jsonb, + check (json_exists( addresses, '$.main' )) +); +select create_distributed_table('user_profiles', 'id'); +insert into user_profiles (addresses) VALUES (JSON_SCALAR('1')); +insert into user_profiles (addresses) VALUES ('{"main":"value"}'); + +-- we cannot insert because WITH UNIQUE KEYS +insert into user_profiles (addresses) VALUES (JSON ('{"main":"value", "main":"value"}' WITH UNIQUE KEYS)); + +-- we can insert with +insert into user_profiles (addresses) VALUES (JSON ('{"main":"value", "main":"value"}' WITHOUT UNIQUE KEYS)) RETURNING *; + +TRUNCATE user_profiles; +INSERT INTO user_profiles (anyjson) VALUES ('12'), ('"abc"'), ('[1,2,3]'), ('{"a":12}'); +select anyjson, anyjson is json array as json_array, anyjson is json object as json_object, anyjson is json scalar as json_scalar, +anyjson is json with UNIQUE keys +from user_profiles WHERE anyjson IS NOT NULL ORDER BY 1; + +-- use json_query +SELECT i, + json_query('[{"x": "aaa"},{"x": "bbb"},{"x": "ccc"}]'::JSONB, '$[$i].x' passing id AS i RETURNING text omit quotes) +FROM generate_series(0, 3) i +JOIN my_films ON(id = i); + +-- we can use JSON_TABLE in modification queries as well + +-- use log level such that we can see trace changes +SET client_min_messages TO DEBUG1; + +--the JSON_TABLE subquery is recursively planned +UPDATE test_table SET VALUE = 'XXX' FROM( +SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt) as foo WHERE foo.id = test_table.id; + +-- Subquery with JSON table can be pushed down because two distributed tables +-- in the query are joined on distribution column +UPDATE test_table SET VALUE = 'XXX' FROM ( +SELECT my_films.id, jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt) as foo WHERE foo.id = test_table.id; + +-- we can pushdown with CTEs as well +WITH json_cte AS +(SELECT my_films.id, jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt) +UPDATE test_table SET VALUE = 'XYZ' FROM json_cte + WHERE json_cte.id = test_table.id; + + -- we can recursively with CTEs as well +WITH json_cte AS +(SELECT my_films.id as film_id, jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + id FOR ORDINALITY, + title text PATH '$.title', + director text PATH '$.director'))) AS jt ORDER BY jt.id LIMIT 1) +UPDATE test_table SET VALUE = 'XYZ' FROM json_cte + WHERE json_cte.film_id = test_table.id; + +SET client_min_messages TO ERROR; +DROP SCHEMA pg15_json CASCADE;