From 6cb61d57dd0395f5a3cf4a29a013152a4420d7a5 Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Thu, 16 Sep 2021 10:33:01 -0400 Subject: [PATCH 3/4] JSON_TABLE PLAN DEFAULT clause --- doc/src/sgml/func.sgml | 118 ++++++++++++++++--- src/backend/nodes/copyfuncs.c | 4 + src/backend/nodes/equalfuncs.c | 3 + src/backend/nodes/outfuncs.c | 3 + src/backend/nodes/readfuncs.c | 3 + src/backend/parser/gram.y | 41 ++++++- src/backend/parser/parse_jsontable.c | 13 ++- src/backend/utils/adt/jsonpath_exec.c | 120 +++++++++++++++----- src/backend/utils/adt/ruleutils.c | 9 ++ src/include/nodes/parsenodes.h | 13 +++ src/include/nodes/primnodes.h | 3 + src/include/parser/kwlist.h | 1 + src/test/regress/expected/jsonb_sqljson.out | 118 +++++++++++++++++++ src/test/regress/sql/jsonb_sqljson.sql | 65 +++++++++++ src/tools/pgindent/typedefs.list | 1 + 15 files changed, 462 insertions(+), 53 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index c127382fe7..83be7a1013 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -19314,6 +19314,10 @@ FROM JSON_TABLE ( context_item, path_expression PASSING { value AS varname } , ... COLUMNS ( json_table_column , ... ) + + PLAN DEFAULT ( { INNER | OUTER } , { CROSS | UNION } + | { CROSS | UNION } , { INNER | OUTER } ) + ) where json_table_column is: @@ -19375,7 +19379,8 @@ where json_table_column is: The rows produced by JSON_TABLE are laterally joined to the row that generated them, so you do not have to explicitly join the constructed view with the original table holding JSON - data. + data. Optionally, you can specify how to join the columns returned + by NESTED PATH using the PLAN DEFAULT clause. @@ -19388,22 +19393,6 @@ where json_table_column is: the resulting rows are joined to the parent row. - - Columns with parent/child relationship are joined using - LEFT OUTER JOIN, so that the parent row - is always included into the output even if it does not have any child rows - after joining the data returned by NESTED PATH, - with NULL values inserted into the child columns if the corresponding - values are missing. - - - - Sibling columns are joined using - FULL OUTER JOIN ON FALSE, so that both parent and child - rows are included into the output, with NULL values inserted - into both child and parrent columns for all missing values. - - Parameters @@ -19575,6 +19564,10 @@ where json_table_column is: JSON_TABLE expressions in an SQL statement. + + You can use the PLAN DEFAULT clause to define how + to join the columns returned by NESTED PATH clauses. + @@ -19597,8 +19590,99 @@ where json_table_column is: + + + PLAN DEFAULT ( option , ... ) + + + + Defines how to join the data returned by NESTED PATH + clauses to the constructed view. The INNER and + OUTER options define the joining plan for parent/child + columns, while UNION and CROSS + affect the sibling columns. You can override the default plans for all + columns at once. + + + + To join columns with parent/child relationship, you can use: + + + + + INNER + + + + + Use INNER JOIN, so that the parent row + is omitted from the output if it does not have any child rows + after joining the data returned by NESTED PATH. + + + + + + + OUTER + + + + + Use LEFT OUTER JOIN, so that the parent row + is always included into the output even if it does not have any child rows + after joining the data returned by NESTED PATH, with NULL values + inserted into the child columns if the corresponding + values are missing. + + + This is the default option for joining columns with parent/child relationship. + + + + + + + To join sibling columns, you can use: + + + + + + UNION + + + + + Use FULL OUTER JOIN ON FALSE, so that both parent and child + rows are included into the output, with NULL values inserted + into both child and parrent columns for all missing values. + + + This is the default option for joining sibling columns. + + + + + + + CROSS + + + + + Use CROSS JOIN, so that the output includes + a row for every possible combination of rows from the left-hand + and the right-hand columns. + + + + + + + diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 17496205e1..ba67636038 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2679,6 +2679,7 @@ _copyJsonTable(const JsonTable *from) COPY_NODE_FIELD(columns); COPY_NODE_FIELD(on_error); COPY_NODE_FIELD(alias); + COPY_SCALAR_FIELD(join_type); COPY_SCALAR_FIELD(location); return newnode; @@ -2717,6 +2718,8 @@ _copyJsonTableParentNode(const JsonTableParentNode *from) COPY_NODE_FIELD(path); COPY_NODE_FIELD(child); + COPY_SCALAR_FIELD(outerJoin); + COPY_SCALAR_FIELD(unionJoin); COPY_SCALAR_FIELD(colMin); COPY_SCALAR_FIELD(colMax); @@ -2733,6 +2736,7 @@ _copyJsonTableSiblingNode(const JsonTableSiblingNode *from) COPY_NODE_FIELD(larg); COPY_NODE_FIELD(rarg); + COPY_SCALAR_FIELD(cross); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 2904b531c5..070eaaa551 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -152,6 +152,8 @@ _equalJsonTableParentNode(const JsonTableParentNode *a, const JsonTableParentNod { COMPARE_NODE_FIELD(path); COMPARE_NODE_FIELD(child); + COMPARE_SCALAR_FIELD(outerJoin); + COMPARE_SCALAR_FIELD(unionJoin); COMPARE_SCALAR_FIELD(colMin); COMPARE_SCALAR_FIELD(colMax); @@ -163,6 +165,7 @@ _equalJsonTableSiblingNode(const JsonTableSiblingNode *a, const JsonTableSibling { COMPARE_NODE_FIELD(larg); COMPARE_NODE_FIELD(rarg); + COMPARE_SCALAR_FIELD(cross); return true; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 444f1f9a09..f748c36044 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1874,6 +1874,8 @@ _outJsonTableParentNode(StringInfo str, const JsonTableParentNode *node) WRITE_NODE_FIELD(path); WRITE_NODE_FIELD(child); + WRITE_BOOL_FIELD(outerJoin); + WRITE_BOOL_FIELD(unionJoin); WRITE_INT_FIELD(colMin); WRITE_INT_FIELD(colMax); } @@ -1885,6 +1887,7 @@ _outJsonTableSiblingNode(StringInfo str, const JsonTableSiblingNode *node) WRITE_NODE_FIELD(larg); WRITE_NODE_FIELD(rarg); + WRITE_BOOL_FIELD(cross); } /***************************************************************************** diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 285fc0969b..b4f6c55f1c 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1504,6 +1504,8 @@ _readJsonTableParentNode(void) READ_NODE_FIELD(path); READ_NODE_FIELD(child); + READ_BOOL_FIELD(outerJoin); + READ_BOOL_FIELD(unionJoin); READ_INT_FIELD(colMin); READ_INT_FIELD(colMax); @@ -1517,6 +1519,7 @@ _readJsonTableSiblingNode(void) READ_NODE_FIELD(larg); READ_NODE_FIELD(rarg); + READ_BOOL_FIELD(cross); READ_DONE(); } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index bb9dc9e22f..986111800f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -691,6 +691,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type json_encoding json_encoding_clause_opt + json_table_plan_clause_opt + json_table_default_plan + json_table_default_plan_choices + json_table_default_plan_inner_outer + json_table_default_plan_union_cross json_wrapper_clause_opt json_wrapper_behavior json_conditional_or_unconditional_opt @@ -804,7 +809,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ORDER ORDINALITY OTHERS OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER - PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLANS POLICY + PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLAN PLANS POLICY POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION @@ -15682,13 +15687,15 @@ json_table: JSON_TABLE '(' json_api_common_syntax json_table_columns_clause + json_table_plan_clause_opt json_table_error_clause_opt ')' { JsonTable *n = makeNode(JsonTable); n->common = (JsonCommon *) $3; n->columns = $4; - n->on_error = $5; + n->join_type = $5; + n->on_error = $6; n->location = @1; $$ = (Node *) n; } @@ -15825,6 +15832,34 @@ path_opt: | /* EMPTY */ { } ; +json_table_plan_clause_opt: + json_table_default_plan { $$ = $1; } + | /* EMPTY */ { $$ = JSTPJ_OUTER | JSTPJ_UNION; } + ; + +json_table_default_plan: + PLAN DEFAULT '(' json_table_default_plan_choices ')' { $$ = $4; } + ; + +json_table_default_plan_choices: + json_table_default_plan_inner_outer { $$ = $1 | JSTPJ_UNION; } + | json_table_default_plan_inner_outer ',' + json_table_default_plan_union_cross { $$ = $1 | $3; } + | json_table_default_plan_union_cross { $$ = $1 | JSTPJ_OUTER; } + | json_table_default_plan_union_cross ',' + json_table_default_plan_inner_outer { $$ = $1 | $3; } + ; + +json_table_default_plan_inner_outer: + INNER_P { $$ = JSTPJ_INNER; } + | OUTER_P { $$ = JSTPJ_OUTER; } + ; + +json_table_default_plan_union_cross: + UNION { $$ = JSTPJ_UNION; } + | CROSS { $$ = JSTPJ_CROSS; } + ; + json_returning_clause_opt: RETURNING Typename { @@ -16703,6 +16738,7 @@ unreserved_keyword: | PASSING | PASSWORD | PATH + | PLAN | PLANS | POLICY | PRECEDING @@ -17318,6 +17354,7 @@ bare_label_keyword: | PASSWORD | PATH | PLACING + | PLAN | PLANS | POLICY | POSITION diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c index 94c96606c1..41fe7659de 100644 --- a/src/backend/parser/parse_jsontable.c +++ b/src/backend/parser/parse_jsontable.c @@ -174,12 +174,13 @@ transformNestedJsonTableColumn(JsonTableContext *cxt, JsonTableColumn *jtc) } static Node * -makeJsonTableSiblingJoin(Node *lnode, Node *rnode) +makeJsonTableSiblingJoin(bool cross, Node *lnode, Node *rnode) { JsonTableSiblingNode *join = makeNode(JsonTableSiblingNode); join->larg = lnode; join->rarg = rnode; + join->cross = cross; return (Node *) join; } @@ -187,7 +188,7 @@ makeJsonTableSiblingJoin(Node *lnode, Node *rnode) /* * Recursively transform child (nested) JSON_TABLE columns. * - * Child columns are transformed into a binary tree of union-joined + * Child columns are transformed into a binary tree of union/cross-joined * JsonTableSiblingNodes. */ static Node * @@ -195,8 +196,9 @@ transformJsonTableChildColumns(JsonTableContext *cxt, List *columns) { Node *res = NULL; ListCell *lc; + bool cross = cxt->table->join_type & JSTPJ_CROSS; - /* transform all nested columns into union join */ + /* transform all nested columns into union/cros join */ foreach(lc, columns) { JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc)); @@ -208,7 +210,7 @@ transformJsonTableChildColumns(JsonTableContext *cxt, List *columns) node = transformNestedJsonTableColumn(cxt, jtc); /* join transformed node with previous sibling nodes */ - res = res ? makeJsonTableSiblingJoin(res, node) : node; + res = res ? makeJsonTableSiblingJoin(cross, res, node) : node; } return res; @@ -386,6 +388,9 @@ transformJsonTableColumns(JsonTableContext *cxt, List *columns, char *pathSpec, /* transform recursively nested columns */ node->child = transformJsonTableChildColumns(cxt, columns); + node->outerJoin = cxt->table->join_type & JSTPJ_OUTER; + node->unionJoin = cxt->table->join_type & JSTPJ_UNION; + return node; } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index ebfa226bf3..4512c529e7 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -175,6 +175,7 @@ struct JsonTableScanState Datum current; int ordinal; bool currentIsNull; + bool outerJoin; bool errorOnError; bool advanceNested; bool reset; @@ -188,6 +189,7 @@ struct JsonTableJoinState { JsonTableJoinState *left; JsonTableJoinState *right; + bool cross; bool advanceRight; } join; JsonTableScanState scan; @@ -3166,6 +3168,7 @@ JsonTableInitScanState(JsonTableContext *cxt, JsonTableScanState *scan, int i; scan->parent = parent; + scan->outerJoin = node->outerJoin; scan->errorOnError = node->errorOnError; scan->path = DatumGetJsonPathP(node->path->constvalue); scan->args = args; @@ -3192,6 +3195,7 @@ JsonTableInitPlanState(JsonTableContext *cxt, Node *plan, JsonTableSiblingNode *join = castNode(JsonTableSiblingNode, plan); state->is_join = true; + state->u.join.cross = join->cross; state->u.join.left = JsonTableInitPlanState(cxt, join->larg, parent); state->u.join.right = JsonTableInitPlanState(cxt, join->rarg, parent); } @@ -3328,8 +3332,26 @@ JsonTableSetDocument(TableFuncScanState *state, Datum value) JsonTableResetContextItem(&cxt->root, value); } +/* Recursively reset scan and its child nodes */ +static void +JsonTableRescanRecursive(JsonTableJoinState *state) +{ + if (state->is_join) + { + JsonTableRescanRecursive(state->u.join.left); + JsonTableRescanRecursive(state->u.join.right); + state->u.join.advanceRight = false; + } + else + { + JsonTableRescan(&state->u.scan); + if (state->u.scan.nested) + JsonTableRescanRecursive(state->u.scan.nested); + } +} + /* - * Fetch next row from a union joined scan. + * Fetch next row from a cross/union joined scan. * * Returned false at the end of a scan, true otherwise. */ @@ -3339,17 +3361,48 @@ JsonTableNextJoinRow(JsonTableJoinState *state) if (!state->is_join) return JsonTableNextRow(&state->u.scan); - if (!state->u.join.advanceRight) + if (state->u.join.advanceRight) { - /* fetch next outer row */ - if (JsonTableNextJoinRow(state->u.join.left)) + /* fetch next inner row */ + if (JsonTableNextJoinRow(state->u.join.right)) return true; - state->u.join.advanceRight = true; /* next inner row */ + /* inner rows are exhausted */ + if (state->u.join.cross) + state->u.join.advanceRight = false; /* next outer row */ + else + return false; /* end of scan */ } - /* fetch next inner row */ - return JsonTableNextJoinRow(state->u.join.right); + while (!state->u.join.advanceRight) + { + /* fetch next outer row */ + bool left = JsonTableNextJoinRow(state->u.join.left); + + if (state->u.join.cross) + { + if (!left) + return false; /* end of scan */ + + JsonTableRescanRecursive(state->u.join.right); + + if (!JsonTableNextJoinRow(state->u.join.right)) + continue; /* next outer row */ + + state->u.join.advanceRight = true; /* next inner row */ + } + else if (!left) + { + if (!JsonTableNextJoinRow(state->u.join.right)) + return false; /* end of scan */ + + state->u.join.advanceRight = true; /* next inner row */ + } + + break; + } + + return true; } /* Recursively set 'reset' flag of scan and its child nodes */ @@ -3373,16 +3426,13 @@ JsonTableJoinReset(JsonTableJoinState *state) } /* - * Fetch next row from a simple scan with outer joined nested subscans. + * Fetch next row from a simple scan with outer/inner joined nested subscans. * * Returned false at the end of a scan, true otherwise. */ static bool JsonTableNextRow(JsonTableScanState *scan) { - JsonbValue *jbv; - MemoryContext oldcxt; - /* reset context item if requested */ if (scan->reset) { @@ -3394,34 +3444,44 @@ JsonTableNextRow(JsonTableScanState *scan) if (scan->advanceNested) { /* fetch next nested row */ - if (JsonTableNextJoinRow(scan->nested)) - return true; + scan->advanceNested = JsonTableNextJoinRow(scan->nested); - scan->advanceNested = false; + if (scan->advanceNested) + return true; } - /* fetch next row */ - jbv = JsonValueListNext(&scan->found, &scan->iter); - - if (!jbv) + for (;;) { - scan->current = PointerGetDatum(NULL); - scan->currentIsNull = true; - return false; /* end of scan */ - } + /* fetch next row */ + JsonbValue *jbv = JsonValueListNext(&scan->found, &scan->iter); + MemoryContext oldcxt; - /* set current row item */ - oldcxt = MemoryContextSwitchTo(scan->mcxt); - scan->current = JsonbPGetDatum(JsonbValueToJsonb(jbv)); - scan->currentIsNull = false; - MemoryContextSwitchTo(oldcxt); + if (!jbv) + { + scan->current = PointerGetDatum(NULL); + scan->currentIsNull = true; + return false; /* end of scan */ + } - scan->ordinal++; + /* set current row item */ + oldcxt = MemoryContextSwitchTo(scan->mcxt); + scan->current = JsonbPGetDatum(JsonbValueToJsonb(jbv)); + scan->currentIsNull = false; + MemoryContextSwitchTo(oldcxt); + + scan->ordinal++; + + if (!scan->nested) + break; - if (scan->nested) - { JsonTableJoinReset(scan->nested); + scan->advanceNested = JsonTableNextJoinRow(scan->nested); + + if (scan->advanceNested || scan->outerJoin) + break; + + /* state->ordinal--; */ /* skip current outer row, reset counter */ } return true; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index e8a1789e38..ae60d5140c 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -11228,6 +11228,15 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit) get_json_table_columns(tf, root, context, showimplicit); + if (!root->outerJoin || !root->unionJoin) + { + appendStringInfoChar(buf, ' '); + appendContextKeyword(context, "PLAN DEFAULT", 0, 0, 0); + appendStringInfo(buf, "(%s, %s)", + root->outerJoin ? "OUTER" : "INNER", + root->unionJoin ? "UNION" : "CROSS"); + } + if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY) get_json_behavior(jexpr->on_error, context, "ERROR"); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index a1191a601a..f4e9eff9d0 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1655,6 +1655,18 @@ typedef struct JsonTableColumn int location; /* token location, or -1 if unknown */ } JsonTableColumn; +/* + * JsonTablePlanJoinType - + * flags for JSON_TABLE join types representation + */ +typedef enum JsonTablePlanJoinType +{ + JSTPJ_INNER = 0x01, + JSTPJ_OUTER = 0x02, + JSTPJ_CROSS = 0x04, + JSTPJ_UNION = 0x08, +} JsonTablePlanJoinType; + /* * JsonTable - * untransformed representation of JSON_TABLE @@ -1664,6 +1676,7 @@ typedef struct JsonTable NodeTag type; JsonCommon *common; /* common JSON path syntax fields */ List *columns; /* list of JsonTableColumn */ + JsonTablePlanJoinType join_type; /* DEFAULT PLAN join type */ JsonBehavior *on_error; /* ON ERROR behavior, if specified */ Alias *alias; /* table alias in FROM clause */ bool lateral; /* does it have LATERAL prefix? */ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index da8cd5fbd5..be5e0482de 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1476,6 +1476,8 @@ typedef struct JsonTableParentNode NodeTag type; Const *path; /* jsonpath constant */ Node *child; /* nested columns, if any */ + bool outerJoin; /* outer or inner join for nested columns? */ + bool unionJoin; /* union or cross join for nested columns? */ int colMin; /* min column index in the resulting column list */ int colMax; /* max column index in the resulting column list */ bool errorOnError; /* ERROR/EMPTY ON ERROR behavior */ @@ -1490,6 +1492,7 @@ typedef struct JsonTableSiblingNode NodeTag type; Node *larg; /* left join node */ Node *rarg; /* right join node */ + bool cross; /* cross or union join? */ } JsonTableSiblingNode; /* ---------------- diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 15f586d455..6065bbae88 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -334,6 +334,7 @@ PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD, BARE_LABEL) diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out index d89c3bef44..0cd8f73c66 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -1420,6 +1420,124 @@ from 4 | -1 | 2 | (11 rows) +-- default plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' columns ( b int path '$' ), + nested path 'strict $.c[*]' columns ( c int path '$' ) + ) + plan default (outer, union) + ) jt; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | | 10 + 2 | 2 | | + 2 | 2 | | 20 + 3 | 3 | 1 | + 3 | 3 | 2 | + 4 | -1 | 1 | + 4 | -1 | 2 | +(11 rows) + +-- default plan (inner, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' columns ( b int path '$' ), + nested path 'strict $.c[*]' columns ( c int path '$' ) + ) + plan default (inner) + ) jt; + n | a | b | c +---+----+---+---- + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | | 10 + 2 | 2 | | + 2 | 2 | | 20 + 3 | 3 | 1 | + 3 | 3 | 2 | + 4 | -1 | 1 | + 4 | -1 | 2 | +(10 rows) + +-- default plan (inner, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' columns ( b int path '$' ), + nested path 'strict $.c[*]' columns ( c int path '$' ) + ) + plan default (cross, inner) + ) jt; + n | a | b | c +---+---+---+---- + 2 | 2 | 1 | 10 + 2 | 2 | 1 | + 2 | 2 | 1 | 20 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | + 2 | 2 | 2 | 20 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | + 2 | 2 | 3 | 20 +(9 rows) + +-- default plan (outer, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' columns ( b int path '$' ), + nested path 'strict $.c[*]' columns ( c int path '$' ) + ) + plan default (outer, cross) + ) jt; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | + 2 | 2 | 1 | 20 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | + 2 | 2 | 2 | 20 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | + 2 | 2 | 3 | 20 + 3 | 3 | | + 4 | -1 | | +(12 rows) + -- Should succeed (JSON arguments are passed to root and nested paths) SELECT * FROM diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql index 6614dd6e45..cd97ff7f46 100644 --- a/src/test/regress/sql/jsonb_sqljson.sql +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -547,6 +547,71 @@ from ) ) jt; +-- default plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' columns ( b int path '$' ), + nested path 'strict $.c[*]' columns ( c int path '$' ) + ) + plan default (outer, union) + ) jt; + +-- default plan (inner, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' columns ( b int path '$' ), + nested path 'strict $.c[*]' columns ( c int path '$' ) + ) + plan default (inner) + ) jt; + +-- default plan (inner, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' columns ( b int path '$' ), + nested path 'strict $.c[*]' columns ( c int path '$' ) + ) + plan default (cross, inner) + ) jt; + +-- default plan (outer, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' columns ( b int path '$' ), + nested path 'strict $.c[*]' columns ( c int path '$' ) + ) + plan default (outer, cross) + ) jt; + + -- Should succeed (JSON arguments are passed to root and nested paths) SELECT * FROM diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index b421e58aac..1d4a69d95f 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1240,6 +1240,7 @@ JsonTableColumnType JsonTableContext JsonTableJoinState JsonTableParentNode +JsonTablePlanJoinType JsonTableScanState JsonTableSiblingNode JsonTokenType -- 2.31.1