From 72cfa2f9629675cd9a19f84f7c2d66f69461ae12 Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Sat, 5 Mar 2022 09:03:20 -0500 Subject: [PATCH 4/4] JSON_TABLE PLAN clause --- doc/src/sgml/func.sgml | 97 +++- src/backend/nodes/copyfuncs.c | 26 +- src/backend/nodes/equalfuncs.c | 2 +- src/backend/nodes/makefuncs.c | 19 + src/backend/nodes/outfuncs.c | 2 +- src/backend/nodes/readfuncs.c | 2 +- src/backend/parser/gram.y | 101 +++- src/backend/parser/parse_jsontable.c | 324 +++++++++++-- src/backend/utils/adt/ruleutils.c | 57 ++- src/include/nodes/makefuncs.h | 2 + src/include/nodes/nodes.h | 1 + src/include/nodes/parsenodes.h | 31 +- src/include/nodes/primnodes.h | 2 +- src/test/regress/expected/jsonb_sqljson.out | 506 ++++++++++++++++++-- src/test/regress/sql/jsonb_sqljson.sql | 355 ++++++++++++-- src/tools/pgindent/typedefs.list | 2 + 16 files changed, 1389 insertions(+), 140 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index ed530a170c..9c369e1a35 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -19313,9 +19313,10 @@ FROM JSON_TABLE ( - context_item, path_expression PASSING { value AS varname } , ... + context_item, path_expression AS json_path_name PASSING { value AS varname } , ... COLUMNS ( json_table_column , ... ) + PLAN ( json_table_plan ) | PLAN DEFAULT ( { INNER | OUTER } , { CROSS | UNION } | { CROSS | UNION } , { INNER | OUTER } ) @@ -19339,6 +19340,16 @@ where json_table_column is: | NESTED PATH json_path_specification AS path_name COLUMNS ( json_table_column , ... ) | name FOR ORDINALITY + +json_table_plan is: + + json_path_name { OUTER | INNER } json_table_plan_primary + | json_table_plan_primary { UNION json_table_plan_primary } ... + | json_table_plan_primary { CROSS json_table_plan_primary } ... + +json_table_plan_primary is: + + json_path_name | ( json_table_plan ) @@ -19381,7 +19392,7 @@ where json_table_column is: 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. Optionally, you can specify how to join the columns returned - by NESTED PATH using the PLAN DEFAULT clause. + by NESTED PATH using the PLAN clause. @@ -19566,7 +19577,7 @@ where json_table_column is: - You can use the PLAN DEFAULT clause to define how + You can use the PLAN clause to define how to join the columns returned by NESTED PATH clauses. @@ -19593,18 +19604,31 @@ where json_table_column is: - PLAN DEFAULT ( option , ... ) + AS json_path_name - - 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. - + + The optional json_path_name serves as an + identifier of the provided json_path_specification. + The path name must be unique and cannot coincide with column names. + When using the PLAN clause, you must specify the names + for all the paths, including the row pattern. Each path name can appear in + the PLAN clause only once. + + + + + + + PLAN ( json_table_plan ) + + + + + Defines how to join the data returned by NESTED PATH + clauses to the constructed view. + To join columns with parent/child relationship, you can use: @@ -19683,6 +19707,23 @@ where json_table_column is: + + + + PLAN DEFAULT ( option , ... ) + + + + Overrides the default joining plans. 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. + Even though the path names are not included into the PLAN DEFAULT + clause, they must be provided for all the paths to conform to + the SQL/JSON standard. + + + @@ -19714,9 +19755,35 @@ SELECT jt.* FROM - - - + + Find a director that has done films in two different genres: + +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 OUTER ((films1 INNER film1) CROSS (films2 INNER film2))) + ) AS jt + WHERE kind1 > kind2 AND director1 = director2; + + + + diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 1cc8da9662..539dae74d3 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2695,9 +2695,9 @@ _copyJsonTable(const JsonTable *from) COPY_NODE_FIELD(common); COPY_NODE_FIELD(columns); + COPY_NODE_FIELD(plan); COPY_NODE_FIELD(on_error); COPY_NODE_FIELD(alias); - COPY_SCALAR_FIELD(join_type); COPY_SCALAR_FIELD(location); return newnode; @@ -2715,6 +2715,7 @@ _copyJsonTableColumn(const JsonTableColumn *from) COPY_STRING_FIELD(name); COPY_NODE_FIELD(typeName); COPY_STRING_FIELD(pathspec); + COPY_STRING_FIELD(pathname); COPY_SCALAR_FIELD(format); COPY_SCALAR_FIELD(wrapper); COPY_SCALAR_FIELD(omit_quotes); @@ -2726,6 +2727,24 @@ _copyJsonTableColumn(const JsonTableColumn *from) return newnode; } +/* + * _copyJsonTablePlan + */ +static JsonTablePlan * +_copyJsonTablePlan(const JsonTablePlan *from) +{ + JsonTablePlan *newnode = makeNode(JsonTablePlan); + + COPY_SCALAR_FIELD(plan_type); + COPY_SCALAR_FIELD(join_type); + COPY_STRING_FIELD(pathname); + COPY_NODE_FIELD(plan1); + COPY_NODE_FIELD(plan2); + COPY_SCALAR_FIELD(location); + + return newnode; +} + /* * _copyJsonTableParent */ @@ -2735,9 +2754,9 @@ _copyJsonTableParent(const JsonTableParent *from) JsonTableParent *newnode = makeNode(JsonTableParent); COPY_NODE_FIELD(path); + COPY_STRING_FIELD(name); COPY_NODE_FIELD(child); COPY_SCALAR_FIELD(outerJoin); - COPY_SCALAR_FIELD(unionJoin); COPY_SCALAR_FIELD(colMin); COPY_SCALAR_FIELD(colMax); @@ -5886,6 +5905,9 @@ copyObjectImpl(const void *from) case T_JsonTableColumn: retval = _copyJsonTableColumn(from); break; + case T_JsonTablePlan: + retval = _copyJsonTablePlan(from); + break; case T_JsonTableParent: retval = _copyJsonTableParent(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index ae1cca2677..76cd75467d 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -181,9 +181,9 @@ static bool _equalJsonTableParent(const JsonTableParent *a, const JsonTableParent *b) { COMPARE_NODE_FIELD(path); + COMPARE_STRING_FIELD(name); COMPARE_NODE_FIELD(child); COMPARE_SCALAR_FIELD(outerJoin); - COMPARE_SCALAR_FIELD(unionJoin); COMPARE_SCALAR_FIELD(colMin); COMPARE_SCALAR_FIELD(colMax); diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index cd6c300e7b..41e26a0fe6 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -867,6 +867,25 @@ makeJsonBehavior(JsonBehaviorType type, Node *default_expr) return behavior; } +/* + * makeJsonTableJoinedPlan - + * creates a joined JsonTablePlan node + */ +Node * +makeJsonTableJoinedPlan(JsonTablePlanJoinType type, Node *plan1, Node *plan2, + int location) +{ + JsonTablePlan *n = makeNode(JsonTablePlan); + + n->plan_type = JSTP_JOINED; + n->join_type = type; + n->plan1 = castNode(JsonTablePlan, plan1); + n->plan2 = castNode(JsonTablePlan, plan2); + n->location = location; + + return (Node *) n; +} + /* * makeJsonEncoding - * converts JSON encoding name to enum JsonEncoding diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index da920f3d36..70a9aeb372 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1874,9 +1874,9 @@ _outJsonTableParent(StringInfo str, const JsonTableParent *node) WRITE_NODE_TYPE("JSONTABPNODE"); WRITE_NODE_FIELD(path); + WRITE_STRING_FIELD(name); WRITE_NODE_FIELD(child); WRITE_BOOL_FIELD(outerJoin); - WRITE_BOOL_FIELD(unionJoin); WRITE_INT_FIELD(colMin); WRITE_INT_FIELD(colMax); } diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 4a9392647c..de1f6e0043 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1503,9 +1503,9 @@ _readJsonTableParent(void) READ_LOCALS(JsonTableParent); READ_NODE_FIELD(path); + READ_STRING_FIELD(name); READ_NODE_FIELD(child); READ_BOOL_FIELD(outerJoin); - READ_BOOL_FIELD(unionJoin); READ_INT_FIELD(colMin); READ_INT_FIELD(colMax); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 3ee19250a6..8d6adb372a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -676,6 +676,18 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_table_formatted_column_definition json_table_exists_column_definition json_table_nested_columns + json_table_plan_clause_opt + json_table_specific_plan + json_table_plan + json_table_plan_simple + json_table_plan_parent_child + json_table_plan_outer + json_table_plan_inner + json_table_plan_sibling + json_table_plan_union + json_table_plan_cross + json_table_plan_primary + json_table_default_plan %type json_name_and_value_list json_value_expr_list @@ -691,8 +703,6 @@ 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 @@ -15736,7 +15746,7 @@ json_table: JsonTable *n = makeNode(JsonTable); n->common = (JsonCommon *) $3; n->columns = $4; - n->join_type = $5; + n->plan = (JsonTablePlan *) $5; n->on_error = $6; n->location = @1; $$ = (Node *) n; @@ -15858,12 +15868,15 @@ json_table_formatted_column_definition: ; json_table_nested_columns: - NESTED path_opt Sconst json_table_columns_clause + NESTED path_opt Sconst + json_as_path_name_clause_opt + json_table_columns_clause { JsonTableColumn *n = makeNode(JsonTableColumn); n->coltype = JTC_NESTED; n->pathspec = $3; - n->columns = $4; + n->pathname = $4; + n->columns = $5; n->location = @1; $$ = (Node *) n; } @@ -15875,12 +15888,84 @@ path_opt: ; json_table_plan_clause_opt: - json_table_default_plan { $$ = $1; } - | /* EMPTY */ { $$ = JSTPJ_OUTER | JSTPJ_UNION; } + json_table_specific_plan { $$ = $1; } + | json_table_default_plan { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } + ; + +json_table_specific_plan: + PLAN '(' json_table_plan ')' { $$ = $3; } + ; + +json_table_plan: + json_table_plan_simple + | json_table_plan_parent_child + | json_table_plan_sibling + ; + +json_table_plan_simple: + json_table_path_name + { + JsonTablePlan *n = makeNode(JsonTablePlan); + n->plan_type = JSTP_SIMPLE; + n->pathname = $1; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_table_plan_parent_child: + json_table_plan_outer + | json_table_plan_inner + ; + +json_table_plan_outer: + json_table_plan_simple OUTER_P json_table_plan_primary + { $$ = makeJsonTableJoinedPlan(JSTPJ_OUTER, $1, $3, @1); } + ; + +json_table_plan_inner: + json_table_plan_simple INNER_P json_table_plan_primary + { $$ = makeJsonTableJoinedPlan(JSTPJ_INNER, $1, $3, @1); } + ; + +json_table_plan_sibling: + json_table_plan_union + | json_table_plan_cross + ; + +json_table_plan_union: + json_table_plan_primary UNION json_table_plan_primary + { $$ = makeJsonTableJoinedPlan(JSTPJ_UNION, $1, $3, @1); } + | json_table_plan_union UNION json_table_plan_primary + { $$ = makeJsonTableJoinedPlan(JSTPJ_UNION, $1, $3, @1); } + ; + +json_table_plan_cross: + json_table_plan_primary CROSS json_table_plan_primary + { $$ = makeJsonTableJoinedPlan(JSTPJ_CROSS, $1, $3, @1); } + | json_table_plan_cross CROSS json_table_plan_primary + { $$ = makeJsonTableJoinedPlan(JSTPJ_CROSS, $1, $3, @1); } + ; + +json_table_plan_primary: + json_table_plan_simple { $$ = $1; } + | '(' json_table_plan ')' + { + castNode(JsonTablePlan, $2)->location = @1; + $$ = $2; + } ; json_table_default_plan: - PLAN DEFAULT '(' json_table_default_plan_choices ')' { $$ = $4; } + PLAN DEFAULT '(' json_table_default_plan_choices ')' + { + JsonTablePlan *n = makeNode(JsonTablePlan); + n->plan_type = JSTP_DEFAULT; + n->join_type = $4; + n->location = @1; + $$ = (Node *) n; + } ; json_table_default_plan_choices: diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c index d254312609..3a5971628b 100644 --- a/src/backend/parser/parse_jsontable.c +++ b/src/backend/parser/parse_jsontable.c @@ -37,13 +37,16 @@ typedef struct JsonTableContext JsonTable *table; /* untransformed node */ TableFunc *tablefunc; /* transformed node */ List *pathNames; /* list of all path and columns names */ + int pathNameId; /* path name id counter */ Oid contextItemTypid; /* type oid of context item (json/jsonb) */ } JsonTableContext; static JsonTableParent * transformJsonTableColumns(JsonTableContext *cxt, - List *columns, - char *pathSpec, - int location); + JsonTablePlan *plan, + List *columns, + char *pathSpec, + char **pathName, + int location); static Node * makeStringConst(char *str, int location) @@ -154,19 +157,150 @@ registerAllJsonTableColumns(JsonTableContext *cxt, List *columns) JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc)); if (jtc->coltype == JTC_NESTED) + { + if (jtc->pathname) + registerJsonTableColumn(cxt, jtc->pathname); + registerAllJsonTableColumns(cxt, jtc->columns); + } else + { registerJsonTableColumn(cxt, jtc->name); + } + } +} + +/* Generate a new unique JSON_TABLE path name. */ +static char * +generateJsonTablePathName(JsonTableContext *cxt) +{ + char namebuf[32]; + char *name = namebuf; + + do + { + snprintf(namebuf, sizeof(namebuf), "json_table_path_%d", + ++cxt->pathNameId); + } while (isJsonTablePathNameDuplicate(cxt, name)); + + name = pstrdup(name); + cxt->pathNames = lappend(cxt->pathNames, name); + + return name; +} + +/* Collect sibling path names from plan to the specified list. */ +static void +collectSiblingPathsInJsonTablePlan(JsonTablePlan *plan, List **paths) +{ + if (plan->plan_type == JSTP_SIMPLE) + *paths = lappend(*paths, plan->pathname); + else if (plan->plan_type == JSTP_JOINED) + { + if (plan->join_type == JSTPJ_INNER || + plan->join_type == JSTPJ_OUTER) + { + Assert(plan->plan1->plan_type == JSTP_SIMPLE); + *paths = lappend(*paths, plan->plan1->pathname); + } + else if (plan->join_type == JSTPJ_CROSS || + plan->join_type == JSTPJ_UNION) + { + collectSiblingPathsInJsonTablePlan(plan->plan1, paths); + collectSiblingPathsInJsonTablePlan(plan->plan2, paths); + } + else + elog(ERROR, "invalid JSON_TABLE join type %d", + plan->join_type); + } +} + +/* + * Validate child JSON_TABLE plan by checking that: + * - all nested columns have path names specified + * - all nested columns have corresponding node in the sibling plan + * - plan does not contain duplicate or extra nodes + */ +static void +validateJsonTableChildPlan(ParseState *pstate, JsonTablePlan *plan, + List *columns) +{ + ListCell *lc1; + List *siblings = NIL; + int nchildren = 0; + + if (plan) + collectSiblingPathsInJsonTablePlan(plan, &siblings); + + foreach(lc1, columns) + { + JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc1)); + + if (jtc->coltype == JTC_NESTED) + { + ListCell *lc2; + bool found = false; + + if (!jtc->pathname) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("nested JSON_TABLE columns must contain an explicit AS pathname specification if an explicit PLAN clause is used"), + parser_errposition(pstate, jtc->location))); + + /* find nested path name in the list of sibling path names */ + foreach(lc2, siblings) + { + if ((found = !strcmp(jtc->pathname, lfirst(lc2)))) + break; + } + + if (!found) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid JSON_TABLE plan"), + errdetail("plan node for nested path %s was not found in plan", jtc->pathname), + parser_errposition(pstate, jtc->location))); + + nchildren++; + } } + + if (list_length(siblings) > nchildren) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid JSON_TABLE plan"), + errdetail("plan node contains some extra or duplicate sibling nodes"), + parser_errposition(pstate, plan ? plan->location : -1))); +} + +static JsonTableColumn * +findNestedJsonTableColumn(List *columns, const char *pathname) +{ + ListCell *lc; + + foreach(lc, columns) + { + JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc)); + + if (jtc->coltype == JTC_NESTED && + jtc->pathname && + !strcmp(jtc->pathname, pathname)) + return jtc; + } + + return NULL; } static Node * -transformNestedJsonTableColumn(JsonTableContext *cxt, JsonTableColumn *jtc) +transformNestedJsonTableColumn(JsonTableContext *cxt, JsonTableColumn *jtc, + JsonTablePlan *plan) { JsonTableParent *node; + char *pathname = jtc->pathname; - node = transformJsonTableColumns(cxt, jtc->columns, jtc->pathspec, - jtc->location); + node = transformJsonTableColumns(cxt, plan, jtc->columns, jtc->pathspec, + &pathname, jtc->location); + node->name = pstrdup(pathname); return (Node *) node; } @@ -184,34 +318,78 @@ makeJsonTableSiblingJoin(bool cross, Node *lnode, Node *rnode) } /* - * Recursively transform child (nested) JSON_TABLE columns. + * Recursively transform child JSON_TABLE plan. * - * Child columns are transformed into a binary tree of union/cross-joined - * JsonTableSiblings. + * Default plan is transformed into a cross/union join of its nested columns. + * Simple and outer/inner plans are transformed into a JsonTableParent by + * finding and transforming corresponding nested column. + * Sibling plans are recursively transformed into a JsonTableSibling. */ static Node * -transformJsonTableChildColumns(JsonTableContext *cxt, List *columns) +transformJsonTableChildPlan(JsonTableContext *cxt, JsonTablePlan *plan, + List *columns) { - Node *res = NULL; - ListCell *lc; - bool cross = cxt->table->join_type & JSTPJ_CROSS; + JsonTableColumn *jtc = NULL; - /* transform all nested columns into union/cros join */ - foreach(lc, columns) + if (!plan || plan->plan_type == JSTP_DEFAULT) { - JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc)); - Node *node; + /* unspecified or default plan */ + Node *res = NULL; + ListCell *lc; + bool cross = plan && (plan->join_type & JSTPJ_CROSS); - if (jtc->coltype != JTC_NESTED) - continue; + /* transform all nested columns into cross/union join */ + foreach(lc, columns) + { + JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc)); + Node *node; - node = transformNestedJsonTableColumn(cxt, jtc); + if (jtc->coltype != JTC_NESTED) + continue; + + node = transformNestedJsonTableColumn(cxt, jtc, plan); + + /* join transformed node with previous sibling nodes */ + res = res ? makeJsonTableSiblingJoin(cross, res, node) : node; + } - /* join transformed node with previous sibling nodes */ - res = res ? makeJsonTableSiblingJoin(cross, res, node) : node; + return res; + } + else if (plan->plan_type == JSTP_SIMPLE) + { + jtc = findNestedJsonTableColumn(columns, plan->pathname); } + else if (plan->plan_type == JSTP_JOINED) + { + if (plan->join_type == JSTPJ_INNER || + plan->join_type == JSTPJ_OUTER) + { + Assert(plan->plan1->plan_type == JSTP_SIMPLE); + jtc = findNestedJsonTableColumn(columns, plan->plan1->pathname); + } + else + { + Node *node1 = + transformJsonTableChildPlan(cxt, plan->plan1, columns); + Node *node2 = + transformJsonTableChildPlan(cxt, plan->plan2, columns); + + return makeJsonTableSiblingJoin(plan->join_type == JSTPJ_CROSS, + node1, node2); + } + } + else + elog(ERROR, "invalid JSON_TABLE plan type %d", plan->plan_type); - return res; + if (!jtc) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid JSON_TABLE plan"), + errdetail("path name was %s not found in nested columns list", + plan->pathname), + parser_errposition(cxt->pstate, plan->location))); + + return transformNestedJsonTableColumn(cxt, jtc, plan); } /* Check whether type is json/jsonb, array, or record. */ @@ -375,19 +553,80 @@ makeParentJsonTableNode(JsonTableContext *cxt, char *pathSpec, List *columns) } static JsonTableParent * -transformJsonTableColumns(JsonTableContext *cxt, List *columns, char *pathSpec, +transformJsonTableColumns(JsonTableContext *cxt, JsonTablePlan *plan, + List *columns, char *pathSpec, char **pathName, int location) { JsonTableParent *node; + JsonTablePlan *childPlan; + bool defaultPlan = !plan || plan->plan_type == JSTP_DEFAULT; + + if (!*pathName) + { + if (cxt->table->plan) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid JSON_TABLE expression"), + errdetail("JSON_TABLE columns must contain " + "explicit AS pathname specification if " + "explicit PLAN clause is used"), + parser_errposition(cxt->pstate, location))); + + *pathName = generateJsonTablePathName(cxt); + } + + if (defaultPlan) + childPlan = plan; + else + { + /* validate parent and child plans */ + JsonTablePlan *parentPlan; + + if (plan->plan_type == JSTP_JOINED) + { + if (plan->join_type != JSTPJ_INNER && + plan->join_type != JSTPJ_OUTER) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid JSON_TABLE plan"), + errdetail("expected INNER or OUTER JSON_TABLE plan node"), + parser_errposition(cxt->pstate, plan->location))); + + parentPlan = plan->plan1; + childPlan = plan->plan2; + + Assert(parentPlan->plan_type != JSTP_JOINED); + Assert(parentPlan->pathname); + } + else + { + parentPlan = plan; + childPlan = NULL; + } + + if (strcmp(parentPlan->pathname, *pathName)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid JSON_TABLE plan"), + errdetail("path name mismatch: expected %s but %s is given", + *pathName, parentPlan->pathname), + parser_errposition(cxt->pstate, plan->location))); + + validateJsonTableChildPlan(cxt->pstate, childPlan, columns); + } /* transform only non-nested columns */ node = makeParentJsonTableNode(cxt, pathSpec, columns); + node->name = pstrdup(*pathName); - /* 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; + if (childPlan || defaultPlan) + { + /* transform recursively nested columns */ + node->child = transformJsonTableChildPlan(cxt, childPlan, columns); + if (node->child) + node->outerJoin = !plan || (plan->join_type & JSTPJ_OUTER); + /* else: default plan case, no children found */ + } return node; } @@ -405,7 +644,9 @@ transformJsonTable(ParseState *pstate, JsonTable *jt) JsonTableContext cxt; TableFunc *tf = makeNode(TableFunc); JsonFuncExpr *jfe = makeNode(JsonFuncExpr); + JsonTablePlan *plan = jt->plan; JsonCommon *jscommon; + char *rootPathName = jt->common->pathname; char *rootPath; bool is_lateral; @@ -413,9 +654,31 @@ transformJsonTable(ParseState *pstate, JsonTable *jt) cxt.table = jt; cxt.tablefunc = tf; cxt.pathNames = NIL; + cxt.pathNameId = 0; + + if (rootPathName) + registerJsonTableColumn(&cxt, rootPathName); registerAllJsonTableColumns(&cxt, jt->columns); +#if 0 /* XXX it' unclear from the standard whether root path name is mandatory or not */ + if (plan && plan->plan_type != JSTP_DEFAULT && !rootPathName) + { + /* Assign root path name and create corresponding plan node */ + JsonTablePlan *rootNode = makeNode(JsonTablePlan); + JsonTablePlan *rootPlan = (JsonTablePlan *) + makeJsonTableJoinedPlan(JSTPJ_OUTER, (Node *) rootNode, + (Node *) plan, jt->location); + + rootPathName = generateJsonTablePathName(&cxt); + + rootNode->plan_type = JSTP_SIMPLE; + rootNode->pathname = rootPathName; + + plan = rootPlan; + } +#endif + jscommon = copyObject(jt->common); jscommon->pathspec = makeStringConst(pstrdup("$"), -1); @@ -451,7 +714,8 @@ transformJsonTable(ParseState *pstate, JsonTable *jt) rootPath = castNode(A_Const, jt->common->pathspec)->val.sval.sval; - tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns, rootPath, + tf->plan = (Node *) transformJsonTableColumns(&cxt, plan, jt->columns, + rootPath, &rootPathName, jt->common->location); tf->ordinalitycol = -1; /* undefine ordinality column number */ diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 46a492dc95..5ad9d01398 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -11131,10 +11131,54 @@ get_json_table_nested_columns(TableFunc *tf, Node *node, appendStringInfoChar(context->buf, ' '); appendContextKeyword(context, "NESTED PATH ", 0, 0, 0); get_const_expr(n->path, context, -1); + appendStringInfo(context->buf, " AS %s", quote_identifier(n->name)); get_json_table_columns(tf, n, context, showimplicit); } } +/* + * get_json_table_plan - Parse back a JSON_TABLE plan + */ +static void +get_json_table_plan(TableFunc *tf, Node *node, deparse_context *context, + bool parenthesize) +{ + if (parenthesize) + appendStringInfoChar(context->buf, '('); + + if (IsA(node, JsonTableSibling)) + { + JsonTableSibling *n = (JsonTableSibling *) node; + + get_json_table_plan(tf, n->larg, context, + IsA(n->larg, JsonTableSibling) || + castNode(JsonTableParent, n->larg)->child); + + appendStringInfoString(context->buf, n->cross ? " CROSS " : " UNION "); + + get_json_table_plan(tf, n->rarg, context, + IsA(n->rarg, JsonTableSibling) || + castNode(JsonTableParent, n->rarg)->child); + } + else + { + JsonTableParent *n = castNode(JsonTableParent, node); + + appendStringInfoString(context->buf, quote_identifier(n->name)); + + if (n->child) + { + appendStringInfoString(context->buf, + n->outerJoin ? " OUTER " : " INNER "); + get_json_table_plan(tf, n->child, context, + IsA(n->child, JsonTableSibling)); + } + } + + if (parenthesize) + appendStringInfoChar(context->buf, ')'); +} + /* * get_json_table_columns - Parse back JSON_TABLE columns */ @@ -11263,6 +11307,8 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit) get_const_expr(root->path, context, -1); + appendStringInfo(buf, " AS %s", quote_identifier(root->name)); + if (jexpr->passing_values) { ListCell *lc1, *lc2; @@ -11295,14 +11341,9 @@ 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"); - } + appendStringInfoChar(buf, ' '); + appendContextKeyword(context, "PLAN ", 0, 0, 0); + get_json_table_plan(tf, (Node *) root, context, true); if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY) get_json_behavior(jexpr->on_error, context, "ERROR"); diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 872f2f0828..c717468eb3 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -110,6 +110,8 @@ extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding, int location); extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format); extern JsonBehavior *makeJsonBehavior(JsonBehaviorType type, Node *expr); +extern Node *makeJsonTableJoinedPlan(JsonTablePlanJoinType type, + Node *plan1, Node *plan2, int location); extern Node *makeJsonKeyValue(Node *key, Node *value); extern Node *makeJsonIsPredicate(Node *expr, JsonFormat *format, JsonValueType vtype, bool unique_keys, diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 58b61cf3ab..b883b6e39f 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -513,6 +513,7 @@ typedef enum NodeTag T_JsonIsPredicate, T_JsonTable, T_JsonTableColumn, + T_JsonTablePlan, T_JsonCommon, T_JsonArgument, T_JsonKeyValue, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 1fa4c1e6bb..3926750399 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1649,6 +1649,7 @@ typedef struct JsonTableColumn char *name; /* column name */ TypeName *typeName; /* column type name */ JsonPathSpec pathspec; /* path specification, if any */ + char *pathname; /* path name, if any */ JsonFormat *format; /* JSON format clause, if specified */ JsonWrapper wrapper; /* WRAPPER behavior for formatted columns */ bool omit_quotes; /* omit or keep quotes on scalar strings? */ @@ -1658,6 +1659,17 @@ typedef struct JsonTableColumn int location; /* token location, or -1 if unknown */ } JsonTableColumn; +/* + * JsonTablePlanType - + * flags for JSON_TABLE plan node types representation + */ +typedef enum JsonTablePlanType +{ + JSTP_DEFAULT, + JSTP_SIMPLE, + JSTP_JOINED, +} JsonTablePlanType; + /* * JsonTablePlanJoinType - * flags for JSON_TABLE join types representation @@ -1670,6 +1682,23 @@ typedef enum JsonTablePlanJoinType JSTPJ_UNION = 0x08, } JsonTablePlanJoinType; +typedef struct JsonTablePlan JsonTablePlan; + +/* + * JsonTablePlan - + * untransformed representation of JSON_TABLE plan node + */ +struct JsonTablePlan +{ + NodeTag type; + JsonTablePlanType plan_type; /* plan type */ + JsonTablePlanJoinType join_type; /* join type (for joined plan only) */ + JsonTablePlan *plan1; /* first joined plan */ + JsonTablePlan *plan2; /* second joined plan */ + char *pathname; /* path name (for simple plan only) */ + int location; /* token location, or -1 if unknown */ +}; + /* * JsonTable - * untransformed representation of JSON_TABLE @@ -1679,7 +1708,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 */ + JsonTablePlan *plan; /* join plan, if specified */ JsonBehavior *on_error; /* ON ERROR behavior, if specified */ Alias *alias; /* table alias in FROM clause */ bool lateral; /* does it have LATERAL prefix? */ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 098f4c96ab..01d0f76df3 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1473,9 +1473,9 @@ typedef struct JsonTableParent { NodeTag type; Const *path; /* jsonpath constant */ + char *name; /* path name */ 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 */ diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out index 0d8c5d6add..2db7023206 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -1136,18 +1136,18 @@ SELECT * FROM ia int[] PATH '$', ta text[] PATH '$', jba jsonb[] PATH '$', - NESTED PATH '$[1]' COLUMNS ( + NESTED PATH '$[1]' AS p1 COLUMNS ( a1 int, - NESTED PATH '$[*]' COLUMNS ( + NESTED PATH '$[*]' AS "p1 1" COLUMNS ( a11 text ), b1 text ), - NESTED PATH '$[2]' COLUMNS ( - NESTED PATH '$[*]' COLUMNS ( + NESTED PATH '$[2]' AS p2 COLUMNS ( + NESTED PATH '$[*]' AS "p2:1" COLUMNS ( a21 text ), - NESTED PATH '$[*]' COLUMNS ( + NESTED PATH '$[*]' AS p22 COLUMNS ( a22 text ) ) @@ -1187,7 +1187,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS "json_table".a21, "json_table".a22 FROM JSON_TABLE( - 'null'::jsonb, '$[*]' + 'null'::jsonb, '$[*]' AS json_table_path_1 PASSING 1 + 2 AS a, '"foo"'::json AS "b c" @@ -1218,34 +1218,35 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS ia integer[] PATH '$', ta text[] PATH '$', jba jsonb[] PATH '$', - NESTED PATH '$[1]' + NESTED PATH '$[1]' AS p1 COLUMNS ( a1 integer PATH '$."a1"', b1 text PATH '$."b1"', - NESTED PATH '$[*]' + NESTED PATH '$[*]' AS "p1 1" COLUMNS ( a11 text PATH '$."a11"' ) ), - NESTED PATH '$[2]' + NESTED PATH '$[2]' AS p2 COLUMNS ( - NESTED PATH '$[*]' + NESTED PATH '$[*]' AS "p2:1" COLUMNS ( a21 text PATH '$."a21"' ), - NESTED PATH '$[*]' + NESTED PATH '$[*]' AS p22 COLUMNS ( a22 text PATH '$."a22"' ) ) ) + PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) ) EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Table Function Scan on "json_table" Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".domain, "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1, "json_table".exists1, "json_table".exists2, "json_table".exists3, "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".ia, "json_table".ta, "json_table".jba, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22 - Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb PATH '$', jsbq jsonb PATH '$' OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR, js2 json PATH '$', jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER, jsb2q jsonb PATH '$' OMIT QUOTES, ia integer[] PATH '$', ta text[] PATH '$', jba jsonb[] PATH '$', NESTED PATH '$[1]' COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' COLUMNS ( NESTED PATH '$[*]' COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' COLUMNS (a22 text PATH '$."a22"')))) + Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb PATH '$', jsbq jsonb PATH '$' OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR, js2 json PATH '$', jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER, jsb2q jsonb PATH '$' OMIT QUOTES, ia integer[] PATH '$', ta text[] PATH '$', jba jsonb[] PATH '$', NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22))))) (3 rows) DROP VIEW jsonb_table_view; @@ -1337,13 +1338,49 @@ ERROR: cannot cast type boolean to jsonb LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EX... ^ -- JSON_TABLE: nested paths and plans +-- Should fail (JSON_TABLE columns must contain explicit AS path +-- specifications if explicit PLAN clause is used) +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' -- AS required here + COLUMNS ( + foo int PATH '$' + ) + PLAN DEFAULT (UNION) +) jt; +ERROR: invalid JSON_TABLE expression +LINE 2: jsonb '[]', '$' -- AS required here + ^ +DETAIL: JSON_TABLE columns must contain explicit AS pathname specification if explicit PLAN clause is used +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' AS path1 + COLUMNS ( + NESTED PATH '$' COLUMNS ( -- AS required here + foo int PATH '$' + ) + ) + PLAN DEFAULT (UNION) +) jt; +ERROR: invalid JSON_TABLE expression +LINE 4: NESTED PATH '$' COLUMNS ( -- AS required here + ^ +DETAIL: JSON_TABLE columns must contain explicit AS pathname specification if explicit PLAN clause is used -- Should fail (column names must be distinct) SELECT * FROM JSON_TABLE( - jsonb '[]', '$' + jsonb '[]', '$' AS a COLUMNS ( - a int, - b text, - a jsonb + a int + ) +) jt; +ERROR: duplicate JSON_TABLE column name: a +HINT: JSON_TABLE column names must be distinct from one another +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' AS a + COLUMNS ( + b int, + NESTED PATH '$' AS a + COLUMNS ( + c int + ) ) ) jt; ERROR: duplicate JSON_TABLE column name: a @@ -1352,10 +1389,9 @@ SELECT * FROM JSON_TABLE( jsonb '[]', '$' COLUMNS ( b int, - NESTED PATH '$' + NESTED PATH '$' AS b COLUMNS ( - c int, - b text + c int ) ) ) jt; @@ -1364,22 +1400,209 @@ HINT: JSON_TABLE column names must be distinct from one another SELECT * FROM JSON_TABLE( jsonb '[]', '$' COLUMNS ( - NESTED PATH '$' + NESTED PATH '$' AS a COLUMNS ( b int ), NESTED PATH '$' COLUMNS ( - NESTED PATH '$' + NESTED PATH '$' AS a COLUMNS ( - c int, - b text + c int ) ) ) ) jt; -ERROR: duplicate JSON_TABLE column name: b +ERROR: duplicate JSON_TABLE column name: a HINT: JSON_TABLE column names must be distinct from one another +-- JSON_TABLE: plan validation +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p1) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 12: PLAN (p1) + ^ +DETAIL: path name mismatch: expected p0 but p1 is given +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 4: NESTED PATH '$' AS p1 COLUMNS ( + ^ +DETAIL: plan node for nested path p1 was not found in plan +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER p3) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 4: NESTED PATH '$' AS p1 COLUMNS ( + ^ +DETAIL: plan node for nested path p1 was not found in plan +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 UNION p1 UNION p11) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 12: PLAN (p0 UNION p1 UNION p11) + ^ +DETAIL: expected INNER or OUTER JSON_TABLE plan node +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER (p1 CROSS p13)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 8: NESTED PATH '$' AS p2 COLUMNS ( + ^ +DETAIL: plan node for nested path p2 was not found in plan +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER (p1 CROSS p2)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 5: NESTED PATH '$' AS p11 COLUMNS ( foo int ), + ^ +DETAIL: plan node for nested path p11 was not found in plan +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 UNION p11) CROSS p2)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 12: PLAN (p0 OUTER ((p1 UNION p11) CROSS p2)) + ^ +DETAIL: plan node contains some extra or duplicate sibling nodes +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER p11) CROSS p2)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 6: NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ^ +DETAIL: plan node for nested path p12 was not found in plan +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 9: NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ^ +DETAIL: plan node for nested path p21 was not found in plan +SELECT * FROM JSON_TABLE( + jsonb 'null', 'strict $[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))) +) jt; + bar | foo | baz +-----+-----+----- +(0 rows) + +SELECT * FROM JSON_TABLE( + jsonb 'null', 'strict $[*]' -- without root path name + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)) +) jt; +ERROR: invalid JSON_TABLE expression +LINE 2: jsonb 'null', 'strict $[*]' -- without root path name + ^ +DETAIL: JSON_TABLE columns must contain explicit AS pathname specification if explicit PLAN clause is used -- JSON_TABLE: plan execution CREATE TEMP TABLE jsonb_table_test (js jsonb); INSERT INTO jsonb_table_test @@ -1397,12 +1620,12 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' + jtt.js,'strict $[*]' as p 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 '$' ) + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) ) ) jt; n | a | b | c @@ -1426,12 +1649,12 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' + jtt.js,'strict $[*]' as p 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 '$' ) + 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; @@ -1450,18 +1673,78 @@ from 4 | -1 | 2 | (11 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; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | | 10 + 2 | 2 | | + 2 | 2 | | 20 + 3 | 3 | 1 | + 3 | 3 | 2 | + 4 | -1 | 1 | + 4 | -1 | 2 | +(11 rows) + +-- specific plan (p outer (pc union pb)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pc union pb)) + ) jt; + n | a | c | b +---+----+----+--- + 1 | 1 | | + 2 | 2 | 10 | + 2 | 2 | | + 2 | 2 | 20 | + 2 | 2 | | 1 + 2 | 2 | | 2 + 2 | 2 | | 3 + 3 | 3 | | 1 + 3 | 3 | | 2 + 4 | -1 | | 1 + 4 | -1 | | 2 +(11 rows) + -- default plan (inner, union) select jt.* from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' + jtt.js,'strict $[*]' as p 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 '$' ) + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) ) plan default (inner) ) jt; @@ -1479,18 +1762,47 @@ from 4 | -1 | 2 | (10 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; + 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 $[*]' + jtt.js,'strict $[*]' as p 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 '$' ) + 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; @@ -1507,18 +1819,46 @@ from 2 | 2 | 3 | 20 (9 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; + 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 $[*]' + jtt.js,'strict $[*]' as p 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 '$' ) + 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; @@ -1538,6 +1878,90 @@ from 4 | -1 | | (12 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; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | + 2 | 2 | 1 | 20 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | + 2 | 2 | 2 | 20 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | + 2 | 2 | 3 | 20 + 3 | 3 | | + 4 | -1 | | +(12 rows) + +select + jt.*, b1 + 100 as b +from + json_table (jsonb + '[ + {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]}, + {"a": 2, "b": [10, 20], "c": [1, null, 2]}, + {"x": "3", "b": [11, 22, 33, 44]} + ]', + '$[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on error, + nested path 'strict $.b[*]' as pb columns ( + b text format json path '$', + nested path 'strict $[*]' as pb1 columns ( + b1 int path '$' + ) + ), + nested path 'strict $.c[*]' as pc columns ( + c text format json path '$', + nested path 'strict $[*]' as pc1 columns ( + c1 int path '$' + ) + ) + ) + --plan default(outer, cross) + plan(p outer ((pb inner pb1) cross (pc outer pc1))) + ) jt; + n | a | b | b1 | c | c1 | b +---+---+--------------+-----+------+----+----- + 1 | 1 | [1, 10] | 1 | 1 | | 101 + 1 | 1 | [1, 10] | 1 | null | | 101 + 1 | 1 | [1, 10] | 1 | 2 | | 101 + 1 | 1 | [1, 10] | 10 | 1 | | 110 + 1 | 1 | [1, 10] | 10 | null | | 110 + 1 | 1 | [1, 10] | 10 | 2 | | 110 + 1 | 1 | [2] | 2 | 1 | | 102 + 1 | 1 | [2] | 2 | null | | 102 + 1 | 1 | [2] | 2 | 2 | | 102 + 1 | 1 | [3, 30, 300] | 3 | 1 | | 103 + 1 | 1 | [3, 30, 300] | 3 | null | | 103 + 1 | 1 | [3, 30, 300] | 3 | 2 | | 103 + 1 | 1 | [3, 30, 300] | 30 | 1 | | 130 + 1 | 1 | [3, 30, 300] | 30 | null | | 130 + 1 | 1 | [3, 30, 300] | 30 | 2 | | 130 + 1 | 1 | [3, 30, 300] | 300 | 1 | | 400 + 1 | 1 | [3, 30, 300] | 300 | null | | 400 + 1 | 1 | [3, 30, 300] | 300 | 2 | | 400 + 2 | 2 | | | | | + 3 | | | | | | +(20 rows) + -- Should succeed (JSON arguments are passed to root and nested paths) SELECT * FROM diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql index 9ad1047485..930d86c90e 100644 --- a/src/test/regress/sql/jsonb_sqljson.sql +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -414,18 +414,18 @@ SELECT * FROM ta text[] PATH '$', jba jsonb[] PATH '$', - NESTED PATH '$[1]' COLUMNS ( + NESTED PATH '$[1]' AS p1 COLUMNS ( a1 int, - NESTED PATH '$[*]' COLUMNS ( + NESTED PATH '$[*]' AS "p1 1" COLUMNS ( a11 text ), b1 text ), - NESTED PATH '$[2]' COLUMNS ( - NESTED PATH '$[*]' COLUMNS ( + NESTED PATH '$[2]' AS p2 COLUMNS ( + NESTED PATH '$[*]' AS "p2:1" COLUMNS ( a21 text ), - NESTED PATH '$[*]' COLUMNS ( + NESTED PATH '$[*]' AS p22 COLUMNS ( a22 text ) ) @@ -478,13 +478,42 @@ SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EXISTS PATH '$.a')); -- JSON_TABLE: nested paths and plans +-- Should fail (JSON_TABLE columns must contain explicit AS path +-- specifications if explicit PLAN clause is used) +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' -- AS required here + COLUMNS ( + foo int PATH '$' + ) + PLAN DEFAULT (UNION) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' AS path1 + COLUMNS ( + NESTED PATH '$' COLUMNS ( -- AS required here + foo int PATH '$' + ) + ) + PLAN DEFAULT (UNION) +) jt; + -- Should fail (column names must be distinct) SELECT * FROM JSON_TABLE( - jsonb '[]', '$' + jsonb '[]', '$' AS a + COLUMNS ( + a int + ) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' AS a COLUMNS ( - a int, - b text, - a jsonb + b int, + NESTED PATH '$' AS a + COLUMNS ( + c int + ) ) ) jt; @@ -492,10 +521,9 @@ SELECT * FROM JSON_TABLE( jsonb '[]', '$' COLUMNS ( b int, - NESTED PATH '$' + NESTED PATH '$' AS b COLUMNS ( - c int, - b text + c int ) ) ) jt; @@ -503,21 +531,176 @@ SELECT * FROM JSON_TABLE( SELECT * FROM JSON_TABLE( jsonb '[]', '$' COLUMNS ( - NESTED PATH '$' + NESTED PATH '$' AS a COLUMNS ( b int ), NESTED PATH '$' COLUMNS ( - NESTED PATH '$' + NESTED PATH '$' AS a COLUMNS ( - c int, - b text + c int ) ) ) ) jt; +-- JSON_TABLE: plan validation + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p1) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER p3) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 UNION p1 UNION p11) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER (p1 CROSS p13)) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER (p1 CROSS p2)) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 UNION p11) CROSS p2)) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER p11) CROSS p2)) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2)) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', 'strict $[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', 'strict $[*]' -- without root path name + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)) +) jt; + -- JSON_TABLE: plan execution CREATE TEMP TABLE jsonb_table_test (js jsonb); @@ -538,12 +721,12 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' + jtt.js,'strict $[*]' as p 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 '$' ) + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) ) ) jt; @@ -553,64 +736,174 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' + jtt.js,'strict $[*]' as p 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 '$' ) + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) ) plan default (outer, union) ) jt; +-- specific plan (p outer (pb union pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb union pc)) + ) jt; + +-- specific plan (p outer (pc union pb)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pc union pb)) + ) jt; + -- default plan (inner, union) select jt.* from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' + jtt.js,'strict $[*]' as p 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 '$' ) + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) ) plan default (inner) ) jt; +-- specific plan (p inner (pb union pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb union pc)) + ) jt; + -- default plan (inner, cross) select jt.* from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' + jtt.js,'strict $[*]' as p 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 '$' ) + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) ) plan default (cross, inner) ) jt; +-- specific plan (p inner (pb cross pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb cross pc)) + ) jt; + -- default plan (outer, cross) select jt.* from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' + jtt.js,'strict $[*]' as p 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 '$' ) + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) ) plan default (outer, cross) ) jt; +-- specific plan (p outer (pb cross pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb cross pc)) + ) jt; + + +select + jt.*, b1 + 100 as b +from + json_table (jsonb + '[ + {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]}, + {"a": 2, "b": [10, 20], "c": [1, null, 2]}, + {"x": "3", "b": [11, 22, 33, 44]} + ]', + '$[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on error, + nested path 'strict $.b[*]' as pb columns ( + b text format json path '$', + nested path 'strict $[*]' as pb1 columns ( + b1 int path '$' + ) + ), + nested path 'strict $.c[*]' as pc columns ( + c text format json path '$', + nested path 'strict $[*]' as pc1 columns ( + c1 int path '$' + ) + ) + ) + --plan default(outer, cross) + plan(p outer ((pb inner pb1) cross (pc outer pc1))) + ) jt; -- Should succeed (JSON arguments are passed to root and nested paths) SELECT * diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 04b9698d1f..917ac87b0f 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1242,7 +1242,9 @@ JsonTableColumnType JsonTableContext JsonTableJoinState JsonTableParent +JsonTablePlan JsonTablePlanJoinType +JsonTablePlanType JsonTableScanState JsonTableSibling JsonTokenType -- 2.25.1