diff --git a/doc/src/sgml/func/func-json.sgml b/doc/src/sgml/func/func-json.sgml
index 3d97e2b5375..541d02d4a6e 100644
--- a/doc/src/sgml/func/func-json.sgml
+++ b/doc/src/sgml/func/func-json.sgml
@@ -3663,6 +3663,11 @@ JSON_TABLE (
context_item, path_expression AS json_path_name PASSING { value AS varname } , ...
COLUMNS ( json_table_column , ... )
{ ERROR | EMPTY ARRAY} ON ERROR
+
+ PLAN ( json_table_plan ) |
+ PLAN DEFAULT ( { INNER | OUTER } , { CROSS | UNION }
+ | { CROSS | UNION } , { INNER | OUTER } )
+
)
@@ -3679,6 +3684,16 @@ where json_table_column is:
| name type EXISTS PATH path_expression
{ ERROR | TRUE | FALSE | UNKNOWN } ON ERROR
| NESTED PATH path_expression AS json_path_name COLUMNS ( json_table_column , ... )
+
+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 )
@@ -3842,6 +3857,11 @@ where json_table_column is:
in a single function invocation rather than chaining several
JSON_TABLE expressions in an SQL statement.
+
+
+ You can use the PLAN clause to define how
+ to join the columns returned by NESTED PATH clauses.
+
@@ -3866,9 +3886,127 @@ where json_table_column is:
The optional json_path_name serves as an
- identifier of the provided path_expression.
- The name must be unique and distinct from the column names.
+ identifier of the provided json_path_specification.
+ The path name must be unique and distinct from the column names. Each
+ path name can appear in the PLAN clause only once.
+
+ The SQL/JSON standard requires an explicit path name for every
+ NESTED PATH when a PLAN or
+ PLAN DEFAULT clause is present, while the row pattern
+ path name always remains optional. As an extension,
+ PostgreSQL does not require any path name: a
+ name is generated for any path left unnamed. Note, however, that a
+ specific PLAN (json_table_plan)
+ can only refer to paths by name, so every path that such a plan must
+ mention has to be given a name explicitly.
+
+
+
+
+
+
+ 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:
+
+
+
+
+ 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
+
+
+
+
+ Generate one row for each value produced by each of the sibling
+ columns. The columns from the other siblings are set to null.
+
+
+ This is the default option for joining sibling columns.
+
+
+
+
+
+
+ CROSS
+
+
+
+
+ Generate one row for each combination of values from the sibling columns.
+
+
+
+
+
+
+
+
+
+
+
+ PLAN DEFAULT ( OUTER | INNER , UNION | CROSS )
+
+
+
+ The terms can also be specified in reverse order. The
+ INNER or OUTER option defines the
+ joining plan for parent/child columns, while UNION or
+ CROSS affects joins of sibling columns. This form
+ of PLAN overrides the default plan for
+ all columns at once.
+
+
+ PLAN DEFAULT is simpler than specifying a complete
+ PLAN, and is often all that is required to get the desired
+ output.
+
@@ -4082,5 +4220,39 @@ COLUMNS (
+
+
+ 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;
+
+ director | title1 | kind1 | title2 | kind2
+------------------+---------+----------+--------+--------
+ Alfred Hitchcock | Vertigo | thriller | Psycho | horror
+(1 row)
+
+
+
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index 626054cbcef..55f073e2262 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -650,7 +650,7 @@ T814 Colon in JSON_OBJECT or JSON_OBJECTAGG YES
T821 Basic SQL/JSON query operators YES
T822 SQL/JSON: IS JSON WITH UNIQUE KEYS predicate YES
T823 SQL/JSON: PASSING clause YES
-T824 JSON_TABLE: specific PLAN clause NO
+T824 JSON_TABLE: specific PLAN clause YES
T825 SQL/JSON: ON EMPTY and ON ERROR clauses YES
T826 General value expression in ON ERROR or ON EMPTY clauses YES
T827 JSON_TABLE: sibling NESTED COLUMNS clauses YES
@@ -664,7 +664,7 @@ T834 SQL/JSON path language: wildcard member accessor YES
T835 SQL/JSON path language: filter expressions YES
T836 SQL/JSON path language: starts with predicate YES
T837 SQL/JSON path language: regex_like predicate YES
-T838 JSON_TABLE: PLAN DEFAULT clause NO
+T838 JSON_TABLE: PLAN DEFAULT clause YES
T839 Formatted cast of datetimes to/from character strings NO
T840 Hex integer literals in SQL/JSON path language YES
T851 SQL/JSON: optional keywords for default syntax YES
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 40b09958ac2..cdc02b274ff 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -963,6 +963,60 @@ makeJsonBehavior(JsonBehaviorType btype, Node *expr, int location)
return behavior;
}
+/*
+ * makeJsonTableDefaultPlan -
+ * creates a JsonTablePlanSpec node to represent a "default" JSON_TABLE plan
+ * with given join strategy
+ */
+Node *
+makeJsonTableDefaultPlan(JsonTablePlanJoinType join_type, int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_DEFAULT;
+ n->join_type = join_type;
+ n->location = location;
+
+ return (Node *) n;
+}
+
+/*
+ * makeJsonTableSimplePlan -
+ * creates a JsonTablePlanSpec node to represent a "simple" JSON_TABLE plan
+ * for given PATH
+ */
+Node *
+makeJsonTableSimplePlan(char *pathname, int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_SIMPLE;
+ n->pathname = pathname;
+ n->location = location;
+
+ return (Node *) n;
+}
+
+/*
+ * makeJsonTableJoinedPlan -
+ * creates a JsonTablePlanSpec node to represent join between the given
+ * pair of plans
+ */
+Node *
+makeJsonTableJoinedPlan(JsonTablePlanJoinType type, Node *plan1, Node *plan2,
+ int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_JOINED;
+ n->join_type = type;
+ n->plan1 = castNode(JsonTablePlanSpec, plan1);
+ n->plan2 = castNode(JsonTablePlanSpec, plan2);
+ n->location = location;
+
+ return (Node *) n;
+}
+
/*
* makeJsonKeyValue -
* creates a JsonKeyValue node
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..9e05a314707 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -672,6 +672,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_table
json_table_column_definition
json_table_column_path_clause_opt
+ json_table_plan_clause_opt
+ json_table_plan
+ json_table_plan_simple
+ json_table_plan_outer
+ json_table_plan_inner
+ json_table_plan_union
+ json_table_plan_cross
+ json_table_plan_primary
%type json_name_and_value_list
json_value_expr_list
json_array_aggregate_order_by_clause_opt
@@ -683,6 +691,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type json_behavior_type
json_predicate_type_constraint
json_quotes_clause_opt
+ json_table_default_plan_choices
+ json_table_default_plan_inner_outer
+ json_table_default_plan_union_cross
json_wrapper_behavior
%type json_key_uniqueness_constraint_opt
json_object_constructor_null_clause_opt
@@ -15188,6 +15199,7 @@ json_table:
json_value_expr ',' a_expr json_table_path_name_opt
json_passing_clause_opt
COLUMNS '(' json_table_column_definition_list ')'
+ json_table_plan_clause_opt
json_on_error_clause_opt
')'
{
@@ -15199,13 +15211,15 @@ json_table:
castNode(A_Const, $5)->val.node.type != T_String)
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only string constants are supported in JSON_TABLE path specification"),
+ errmsg("only string constants are supported in"
+ " JSON_TABLE path specification"),
parser_errposition(@5));
pathstring = castNode(A_Const, $5)->val.sval.sval;
n->pathspec = makeJsonTablePathSpec(pathstring, $6, @5, @6);
n->passing = $7;
n->columns = $10;
- n->on_error = (JsonBehavior *) $12;
+ n->planspec = (JsonTablePlanSpec *) $12;
+ n->on_error = (JsonBehavior *) $13;
n->location = @1;
$$ = (Node *) n;
}
@@ -15297,8 +15311,7 @@ json_table_column_definition:
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
- n->pathspec = (JsonTablePathSpec *)
- makeJsonTablePathSpec($3, NULL, @3, -1);
+ n->pathspec = makeJsonTablePathSpec($3, NULL, @3, -1);
n->columns = $6;
n->location = @1;
$$ = (Node *) n;
@@ -15309,8 +15322,7 @@ json_table_column_definition:
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
- n->pathspec = (JsonTablePathSpec *)
- makeJsonTablePathSpec($3, $5, @3, @5);
+ n->pathspec = makeJsonTablePathSpec($3, $5, @3, @5);
n->columns = $8;
n->location = @1;
$$ = (Node *) n;
@@ -15329,6 +15341,83 @@ json_table_column_path_clause_opt:
{ $$ = NULL; }
;
+json_table_plan_clause_opt:
+ PLAN '(' json_table_plan ')'
+ { $$ = $3; }
+ | PLAN DEFAULT '(' json_table_default_plan_choices ')'
+ { $$ = makeJsonTableDefaultPlan($4, @1); }
+ | /* EMPTY */
+ { $$ = NULL; }
+ ;
+
+json_table_plan:
+ json_table_plan_simple
+ | json_table_plan_outer
+ | json_table_plan_inner
+ | json_table_plan_union
+ | json_table_plan_cross
+ ;
+
+json_table_plan_simple:
+ name
+ { $$ = makeJsonTableSimplePlan($1, @1); }
+ ;
+
+json_table_plan_outer:
+ json_table_plan_simple OUTER_P json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_OUTER, $1, $3, @1); }
+ ;
+
+json_table_plan_inner:
+ json_table_plan_simple INNER_P json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_INNER, $1, $3, @1); }
+ ;
+
+json_table_plan_union:
+ json_table_plan_primary UNION json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_UNION, $1, $3, @1); }
+ | json_table_plan_union UNION json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_UNION, $1, $3, @1); }
+ ;
+
+json_table_plan_cross:
+ json_table_plan_primary CROSS json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_CROSS, $1, $3, @1); }
+ | json_table_plan_cross CROSS json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_CROSS, $1, $3, @1); }
+ ;
+
+json_table_plan_primary:
+ json_table_plan_simple
+ { $$ = $1; }
+ | '(' json_table_plan ')'
+ {
+ castNode(JsonTablePlanSpec, $2)->location = @1;
+ $$ = $2;
+ }
+ ;
+
+json_table_default_plan_choices:
+ json_table_default_plan_inner_outer
+ { $$ = $1 | JSTP_JOIN_UNION; }
+ | json_table_default_plan_union_cross
+ { $$ = $1 | JSTP_JOIN_OUTER; }
+ | json_table_default_plan_inner_outer ',' json_table_default_plan_union_cross
+ { $$ = $1 | $3; }
+ | json_table_default_plan_union_cross ',' json_table_default_plan_inner_outer
+ { $$ = $1 | $3; }
+ ;
+
+json_table_default_plan_inner_outer:
+ INNER_P { $$ = JSTP_JOIN_INNER; }
+ | OUTER_P { $$ = JSTP_JOIN_OUTER; }
+ ;
+
+json_table_default_plan_union_cross:
+ UNION { $$ = JSTP_JOIN_UNION; }
+ | CROSS { $$ = JSTP_JOIN_CROSS; }
+ ;
+
/*****************************************************************************
*
* Type syntax
diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c
index 32a1e8629b2..ef042830f02 100644
--- a/src/backend/parser/parse_jsontable.c
+++ b/src/backend/parser/parse_jsontable.c
@@ -39,17 +39,22 @@ typedef struct JsonTableParseContext
} JsonTableParseContext;
static JsonTablePlan *transformJsonTableColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *planspec,
List *columns,
List *passingArgs,
JsonTablePathSpec *pathspec);
static JsonTablePlan *transformJsonTableNestedColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *plan,
List *passingArgs,
List *columns);
static JsonFuncExpr *transformJsonTableColumn(JsonTableColumn *jtc,
Node *contextItemExpr,
- List *passingArgs);
+ List *passingArgs,
+ bool errorOnError);
static bool isCompositeType(Oid typid);
-static JsonTablePlan *makeJsonTablePathScan(JsonTablePathSpec *pathspec,
+static JsonTablePlan *makeJsonTablePathScan(JsonTableParseContext *cxt,
+ JsonTablePathSpec *pathspec,
+ JsonTablePlanSpec *planspec,
bool errorOnError,
int colMin, int colMax,
JsonTablePlan *childplan);
@@ -57,8 +62,14 @@ static void CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
List *columns);
static bool LookupPathOrColumnName(JsonTableParseContext *cxt, char *name);
static char *generateJsonTablePathName(JsonTableParseContext *cxt);
-static JsonTablePlan *makeJsonTableSiblingJoin(JsonTablePlan *lplan,
+static void validateJsonTableChildPlan(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *plan,
+ List *columns);
+static JsonTablePlan *makeJsonTableSiblingJoin(bool cross,
+ JsonTablePlan *lplan,
JsonTablePlan *rplan);
+static void
+ appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingArgs);
/*
* transformJsonTable -
@@ -76,6 +87,7 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
TableFunc *tf;
JsonFuncExpr *jfe;
JsonExpr *je;
+ JsonTablePlanSpec *plan = jt->planspec;
JsonTablePathSpec *rootPathSpec = jt->pathspec;
bool is_lateral;
JsonTableParseContext cxt = {pstate};
@@ -94,8 +106,17 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
parser_errposition(pstate, jt->on_error->location));
cxt.pathNameId = 0;
+
+ /*
+ * Generate a name for the row pattern path if it was not given one. Path
+ * names are optional for every path, including when a PLAN clause is
+ * present; a specific PLAN() can only reference named paths, so an
+ * unnamed path that the plan must mention is caught later as a path name
+ * mismatch or a path not covered by the plan.
+ */
if (rootPathSpec->name == NULL)
rootPathSpec->name = generateJsonTablePathName(&cxt);
+
cxt.pathNames = list_make1(rootPathSpec->name);
CheckDuplicateColumnOrPathNames(&cxt, jt->columns);
@@ -135,7 +156,7 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
*/
cxt.jt = jt;
cxt.tf = tf;
- tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns,
+ tf->plan = (Node *) transformJsonTableColumns(&cxt, plan, jt->columns,
jt->passing,
rootPathSpec);
@@ -246,25 +267,110 @@ generateJsonTablePathName(JsonTableParseContext *cxt)
* their type/collation information to cxt->tf.
*/
static JsonTablePlan *
-transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
+transformJsonTableColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *planspec,
+ List *columns,
List *passingArgs,
JsonTablePathSpec *pathspec)
{
- ParseState *pstate = cxt->pstate;
JsonTable *jt = cxt->jt;
TableFunc *tf = cxt->tf;
- ListCell *col;
- bool ordinality_found = false;
+ JsonTablePathScan *scan;
+ JsonTablePlanSpec *childPlanSpec;
+ bool defaultPlan = planspec == NULL ||
+ planspec->plan_type == JSTP_DEFAULT;
bool errorOnError = jt->on_error &&
jt->on_error->btype == JSON_BEHAVIOR_ERROR;
- Oid contextItemTypid = exprType(tf->docexpr);
int colMin,
colMax;
- JsonTablePlan *childplan;
+ JsonTablePlan *childplan = NULL;
/* Start of column range */
colMin = list_length(tf->colvalexprs);
+ if (defaultPlan)
+ childPlanSpec = planspec;
+ else
+ {
+ /* validate parent and child plans */
+ JsonTablePlanSpec *parentPlanSpec;
+
+ if (planspec->plan_type == JSTP_JOINED)
+ {
+ if (planspec->join_type != JSTP_JOIN_INNER &&
+ planspec->join_type != JSTP_JOIN_OUTER)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("Expected INNER or OUTER."),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ parentPlanSpec = planspec->plan1;
+ childPlanSpec = planspec->plan2;
+
+ Assert(parentPlanSpec->plan_type != JSTP_JOINED);
+ Assert(parentPlanSpec->pathname);
+ }
+ else
+ {
+ parentPlanSpec = planspec;
+ childPlanSpec = NULL;
+ }
+
+ if (strcmp(parentPlanSpec->pathname, pathspec->name) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan"),
+ errdetail("PATH name mismatch: expected %s but %s is given.",
+ pathspec->name, parentPlanSpec->pathname),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ validateJsonTableChildPlan(cxt, childPlanSpec, columns);
+ }
+
+ appendJsonTableColumns(cxt, columns, passingArgs);
+
+ /* End of column range. */
+ if (list_length(tf->colvalexprs) == colMin)
+ {
+ /* No columns in this Scan beside the nested ones. */
+ colMax = colMin = -1;
+ }
+ else
+ colMax = list_length(tf->colvalexprs) - 1;
+
+ if (childPlanSpec || defaultPlan)
+ {
+ /* transform recursively nested columns */
+ childplan = transformJsonTableNestedColumns(cxt, childPlanSpec,
+ columns, passingArgs);
+ }
+
+ /* transform only non-nested columns */
+ scan = (JsonTablePathScan *) makeJsonTablePathScan(cxt, pathspec,
+ planspec,
+ errorOnError,
+ colMin,
+ colMax,
+ childplan);
+
+ return (JsonTablePlan *) scan;
+}
+
+/* Append transformed non-nested JSON_TABLE columns to the TableFunc node */
+static void
+appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingArgs)
+{
+ ListCell *col;
+ ParseState *pstate = cxt->pstate;
+ JsonTable *jt = cxt->jt;
+ TableFunc *tf = cxt->tf;
+ bool ordinality_found = false;
+ JsonBehavior *on_error = jt->on_error;
+ bool errorOnError = on_error &&
+ on_error->btype == JSON_BEHAVIOR_ERROR;
+ Oid contextItemTypid = exprType(tf->docexpr);
+
foreach(col, columns)
{
JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col));
@@ -273,12 +379,9 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
Oid typcoll = InvalidOid;
Node *colexpr;
- if (rawc->coltype != JTC_NESTED)
- {
- Assert(rawc->name);
+ if (rawc->name)
tf->colnames = lappend(tf->colnames,
makeString(pstrdup(rawc->name)));
- }
/*
* Determine the type and typmod for the new column. FOR ORDINALITY
@@ -324,7 +427,7 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
param->typeMod = -1;
jfe = transformJsonTableColumn(rawc, (Node *) param,
- passingArgs);
+ passingArgs, errorOnError);
colexpr = transformExpr(pstate, (Node *) jfe,
EXPR_KIND_FROM_FUNCTION);
@@ -349,22 +452,6 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
tf->colcollations = lappend_oid(tf->colcollations, typcoll);
tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
}
-
- /* End of column range. */
- if (list_length(tf->colvalexprs) == colMin)
- {
- /* No columns in this Scan beside the nested ones. */
- colMax = colMin = -1;
- }
- else
- colMax = list_length(tf->colvalexprs) - 1;
-
- /* Recursively transform nested columns */
- childplan = transformJsonTableNestedColumns(cxt, passingArgs, columns);
-
- /* Create a "parent" scan responsible for all columns handled above. */
- return makeJsonTablePathScan(pathspec, errorOnError, colMin, colMax,
- childplan);
}
/*
@@ -395,7 +482,7 @@ isCompositeType(Oid typid)
*/
static JsonFuncExpr *
transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
- List *passingArgs)
+ List *passingArgs, bool errorOnError)
{
Node *pathspec;
JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
@@ -437,6 +524,8 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
jfexpr->output->returning->format = jtc->format;
jfexpr->on_empty = jtc->on_empty;
jfexpr->on_error = jtc->on_error;
+ if (jfexpr->on_error == NULL && errorOnError)
+ jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL, -1);
jfexpr->quotes = jtc->quotes;
jfexpr->wrapper = jtc->wrapper;
jfexpr->location = jtc->location;
@@ -444,57 +533,137 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
return jfexpr;
}
-/*
- * Recursively transform nested columns and create child plan(s) that will be
- * used to evaluate their row patterns.
- */
-static JsonTablePlan *
-transformJsonTableNestedColumns(JsonTableParseContext *cxt,
- List *passingArgs,
- List *columns)
+static JsonTableColumn *
+findNestedJsonTableColumn(List *columns, const char *pathname)
{
- JsonTablePlan *plan = NULL;
ListCell *lc;
- /*
- * If there are multiple NESTED COLUMNS clauses in 'columns', their
- * respective plans will be combined using a "sibling join" plan, which
- * effectively does a UNION of the sets of rows coming from each nested
- * plan.
- */
foreach(lc, columns)
{
JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
- JsonTablePlan *nested;
- if (jtc->coltype != JTC_NESTED)
- continue;
-
- if (jtc->pathspec->name == NULL)
- jtc->pathspec->name = generateJsonTablePathName(cxt);
-
- nested = transformJsonTableColumns(cxt, jtc->columns, passingArgs,
- jtc->pathspec);
-
- if (plan)
- plan = makeJsonTableSiblingJoin(plan, nested);
- else
- plan = nested;
+ if (jtc->coltype == JTC_NESTED &&
+ jtc->pathspec->name &&
+ !strcmp(jtc->pathspec->name, pathname))
+ return jtc;
}
- return plan;
+ return NULL;
}
/*
- * Create a JsonTablePlan for given path and ON ERROR behavior.
+ * Recursively transform nested columns and create child plan(s) that will be
+ * used to evaluate their row patterns.
*
- * colMin and colMin give the range of columns computed by this scan in the
+ * Default plan is transformed into a cross/union join of its nested columns.
+ * Simple and outer/inner plans are transformed into a JsonTablePlan by
+ * finding and transforming corresponding nested column.
+ * Sibling plans are recursively transformed into a JsonTableSiblingJoin.
+ */
+static JsonTablePlan *
+transformJsonTableNestedColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *planspec,
+ List *columns,
+ List *passingArgs)
+{
+ JsonTableColumn *jtc = NULL;
+
+ if (!planspec || planspec->plan_type == JSTP_DEFAULT)
+ {
+ /* unspecified or default plan */
+ JsonTablePlan *plan = NULL;
+ ListCell *lc;
+ bool cross = planspec && (planspec->join_type & JSTP_JOIN_CROSS);
+
+ /*
+ * If there are multiple NESTED COLUMNS clauses in 'columns', their
+ * respective plans will be combined using a "sibling join" plan,
+ * which effectively does a UNION of the sets of rows coming from each
+ * nested plan.
+ */
+ foreach(lc, columns)
+ {
+ JsonTableColumn *col = castNode(JsonTableColumn, lfirst(lc));
+ JsonTablePlan *nested;
+
+ if (col->coltype != JTC_NESTED)
+ continue;
+
+ if (col->pathspec->name == NULL)
+ {
+ col->pathspec->name = generateJsonTablePathName(cxt);
+ }
+
+ nested = transformJsonTableColumns(cxt, planspec, col->columns,
+ passingArgs,
+ col->pathspec);
+
+ /* Join nested plan with previous sibling nested plans. */
+ if (plan)
+ plan = makeJsonTableSiblingJoin(cross, plan, nested);
+ else
+ plan = nested;
+ }
+
+ return plan;
+ }
+ else if (planspec->plan_type == JSTP_SIMPLE)
+ {
+ jtc = findNestedJsonTableColumn(columns, planspec->pathname);
+ }
+ else if (planspec->plan_type == JSTP_JOINED)
+ {
+ if (planspec->join_type == JSTP_JOIN_INNER ||
+ planspec->join_type == JSTP_JOIN_OUTER)
+ {
+ Assert(planspec->plan1->plan_type == JSTP_SIMPLE);
+ jtc = findNestedJsonTableColumn(columns, planspec->plan1->pathname);
+ }
+ else
+ {
+ JsonTablePlan *lplan = transformJsonTableNestedColumns(cxt,
+ planspec->plan1,
+ columns,
+ passingArgs);
+ JsonTablePlan *rplan = transformJsonTableNestedColumns(cxt,
+ planspec->plan2,
+ columns,
+ passingArgs);
+
+ return makeJsonTableSiblingJoin(planspec->join_type == JSTP_JOIN_CROSS,
+ lplan, rplan);
+ }
+ }
+ else
+ elog(ERROR, "invalid JSON_TABLE plan type %d", planspec->plan_type);
+
+ if (!jtc)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("PATH name was %s not found in nested columns list.",
+ planspec->pathname),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ return transformJsonTableColumns(cxt, planspec, jtc->columns,
+ passingArgs,
+ jtc->pathspec);
+}
+
+/*
+ * Create transformed JSON_TABLE parent plan node by appending all non-nested
+ * columns to the TableFunc node and remembering their indices in the
+ * colvalexprs list.
+ *
+ * colMin and colMax give the range of columns computed by this scan in the
* global flat list of column expressions that will be passed to the
* JSON_TABLE's TableFunc. Both are -1 when all of columns are nested and
* thus computed by 'childplan'.
*/
static JsonTablePlan *
-makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
+makeJsonTablePathScan(JsonTableParseContext *cxt, JsonTablePathSpec *pathspec,
+ JsonTablePlanSpec *planspec,
+ bool errorOnError,
int colMin, int colMax,
JsonTablePlan *childplan)
{
@@ -518,6 +687,11 @@ makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
scan->colMin = colMin;
scan->colMax = colMax;
+ if (scan->child)
+ scan->outerJoin = planspec == NULL ||
+ (planspec->join_type & JSTP_JOIN_OUTER);
+ /* else: default plan case, no children found */
+
return (JsonTablePlan *) scan;
}
@@ -529,13 +703,103 @@ makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
* sets of rows from 'lplan' and 'rplan'.
*/
static JsonTablePlan *
-makeJsonTableSiblingJoin(JsonTablePlan *lplan, JsonTablePlan *rplan)
+makeJsonTableSiblingJoin(bool cross, JsonTablePlan *lplan, JsonTablePlan *rplan)
{
JsonTableSiblingJoin *join = makeNode(JsonTableSiblingJoin);
join->plan.type = T_JsonTableSiblingJoin;
join->lplan = lplan;
join->rplan = rplan;
+ join->cross = cross;
return (JsonTablePlan *) join;
}
+
+/* Collect sibling path names from plan to the specified list. */
+static void
+collectSiblingPathsInJsonTablePlan(JsonTablePlanSpec *plan, List **paths)
+{
+ if (plan->plan_type == JSTP_SIMPLE)
+ *paths = lappend(*paths, plan->pathname);
+ else if (plan->plan_type == JSTP_JOINED)
+ {
+ if (plan->join_type == JSTP_JOIN_INNER ||
+ plan->join_type == JSTP_JOIN_OUTER)
+ {
+ Assert(plan->plan1->plan_type == JSTP_SIMPLE);
+ *paths = lappend(*paths, plan->plan1->pathname);
+ }
+ else if (plan->join_type == JSTP_JOIN_CROSS ||
+ plan->join_type == JSTP_JOIN_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(JsonTableParseContext *cxt, JsonTablePlanSpec *plan,
+ List *columns)
+{
+ ParseState *pstate = cxt->pstate;
+ 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;
+
+ /*
+ * A NESTED path need not be named; generate a name if it was left
+ * unnamed. A generated name cannot be referenced by a specific
+ * PLAN(), so such a path is reported just below as a nested path
+ * not covered by the plan.
+ */
+ if (jtc->pathspec->name == NULL)
+ jtc->pathspec->name = generateJsonTablePathName(cxt);
+
+ /* find nested path name in the list of sibling path names */
+ foreach(lc2, siblings)
+ {
+ if ((found = !strcmp(jtc->pathspec->name, lfirst(lc2))))
+ break;
+ }
+
+ if (!found)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE specification"),
+ errdetail("PLAN clause for nested path %s was not found.",
+ jtc->pathspec->name),
+ parser_errposition(pstate, jtc->location));
+
+ nchildren++;
+ }
+ }
+
+ if (list_length(siblings) > nchildren)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("PLAN clause contains some extra or duplicate sibling nodes."),
+ parser_errposition(pstate, plan ? plan->location : -1));
+}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 9bf8ecdcd0c..b69c87d8587 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -240,6 +240,14 @@ typedef struct JsonTablePlanState
/* Parent plan, if this is a nested plan */
struct JsonTablePlanState *parent;
+
+ /* Join type */
+ bool cross;
+ bool outerJoin;
+ /* Planning control fields */
+ bool advanceNested;
+ bool advanceRight;
+ bool reset;
} JsonTablePlanState;
/* Random number to identify JsonTableExecContext for sanity checking */
@@ -388,13 +396,13 @@ static JsonTablePlanState *JsonTableInitPlan(JsonTableExecContext *cxt,
MemoryContext mcxt);
static void JsonTableSetDocument(TableFuncScanState *state, Datum value);
static void JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item);
+static void JsonTableRescan(JsonTablePlanState *planstate);
static bool JsonTableFetchRow(TableFuncScanState *state);
static Datum JsonTableGetValue(TableFuncScanState *state, int colnum,
Oid typid, int32 typmod, bool *isnull);
static void JsonTableDestroyOpaque(TableFuncScanState *state);
static bool JsonTablePlanScanNextRow(JsonTablePlanState *planstate);
static void JsonTableResetNestedPlan(JsonTablePlanState *planstate);
-static bool JsonTablePlanJoinNextRow(JsonTablePlanState *planstate);
static bool JsonTablePlanNextRow(JsonTablePlanState *planstate);
const TableFuncRoutine JsonbTableRoutine =
@@ -4514,6 +4522,7 @@ JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
JsonTablePathScan *scan = (JsonTablePathScan *) plan;
int i;
+ planstate->outerJoin = scan->outerJoin;
planstate->path = DatumGetJsonPathP(scan->path->value->constvalue);
planstate->args = args;
planstate->mcxt = AllocSetContextCreate(mcxt, "JsonTableExecContext",
@@ -4533,6 +4542,8 @@ JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
{
JsonTableSiblingJoin *join = (JsonTableSiblingJoin *) plan;
+ planstate->cross = join->cross;
+
planstate->left = JsonTableInitPlan(cxt, join->lplan, parentstate,
args, mcxt);
planstate->right = JsonTableInitPlan(cxt, join->rplan, parentstate,
@@ -4587,11 +4598,7 @@ JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
JsonValueListClear(&planstate->found);
}
- /* Reset plan iterator to the beginning of the item list */
- JsonValueListInitIterator(&planstate->found, &planstate->iter);
- planstate->current.value = PointerGetDatum(NULL);
- planstate->current.isnull = true;
- planstate->ordinal = 0;
+ JsonTableRescan(planstate);
}
/*
@@ -4602,16 +4609,93 @@ JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
static bool
JsonTablePlanNextRow(JsonTablePlanState *planstate)
{
- if (IsA(planstate->plan, JsonTablePathScan))
- return JsonTablePlanScanNextRow(planstate);
- else if (IsA(planstate->plan, JsonTableSiblingJoin))
- return JsonTablePlanJoinNextRow(planstate);
- else
- elog(ERROR, "invalid JsonTablePlan %d", (int) planstate->plan->type);
+ if (IsA(planstate->plan, JsonTableSiblingJoin))
+ {
+ if (planstate->advanceRight)
+ {
+ /* fetch next inner row */
+ if (JsonTablePlanNextRow(planstate->right))
+ return true;
- Assert(false);
- /* Appease compiler */
- return false;
+ /* inner rows are exhausted */
+ if (planstate->cross)
+ planstate->advanceRight = false; /* next outer row */
+ else
+ return false; /* end of scan */
+ }
+
+ while (!planstate->advanceRight)
+ {
+ /* fetch next outer row */
+ bool more = JsonTablePlanNextRow(planstate->left);
+
+ if (planstate->cross)
+ {
+ if (!more)
+ return false; /* end of scan */
+
+ JsonTableRescan(planstate->right);
+
+ if (!JsonTablePlanNextRow(planstate->right))
+ continue; /* next outer row */
+
+ planstate->advanceRight = true; /* next inner row */
+ }
+ else if (!more)
+ {
+ if (!JsonTablePlanNextRow(planstate->right))
+ return false; /* end of scan */
+
+ planstate->advanceRight = true; /* next inner row */
+ }
+
+ break;
+ }
+ }
+ else
+ {
+ /* reset context item if requested */
+ if (planstate->reset)
+ {
+ JsonTablePlanState *parent = planstate->parent;
+
+ Assert(parent != NULL && !parent->current.isnull);
+ JsonTableResetRowPattern(planstate, parent->current.value);
+ planstate->reset = false;
+ }
+
+ if (planstate->advanceNested)
+ {
+ /* fetch next nested row */
+ planstate->advanceNested = JsonTablePlanNextRow(planstate->nested);
+ if (planstate->advanceNested)
+ return true;
+ }
+
+ for (;;)
+ {
+ if (!JsonTablePlanScanNextRow(planstate))
+ return false;
+
+ if (planstate->nested == NULL)
+ break;
+
+ JsonTableResetNestedPlan(planstate->nested);
+ planstate->advanceNested = JsonTablePlanNextRow(planstate->nested);
+
+ if (!planstate->advanceNested && !planstate->outerJoin)
+ continue;
+
+ /*
+ * We have a row to return: either the nested plan produced one,
+ * or this is an outer join and we emit the parent row with the
+ * nested columns set to NULL.
+ */
+ break;
+ }
+ }
+
+ return true;
}
/*
@@ -4697,47 +4781,27 @@ JsonTableResetNestedPlan(JsonTablePlanState *planstate)
{
JsonTablePlanState *parent = planstate->parent;
- if (!parent->current.isnull)
- JsonTableResetRowPattern(planstate, parent->current.value);
+ planstate->reset = true;
+ planstate->advanceNested = false;
+
+ if (planstate->nested)
+ JsonTableResetNestedPlan(planstate->nested);
/*
* If this plan itself has a child nested plan, it will be reset when
* the caller calls JsonTablePlanNextRow() on this plan.
*/
+ if (!parent->current.isnull)
+ JsonTableResetRowPattern(planstate, parent->current.value);
}
else if (IsA(planstate->plan, JsonTableSiblingJoin))
{
JsonTableResetNestedPlan(planstate->left);
JsonTableResetNestedPlan(planstate->right);
+ planstate->advanceRight = false;
}
}
-/*
- * Fetch the next row from a JsonTableSiblingJoin.
- *
- * This is essentially a UNION between the rows from left and right siblings.
- */
-static bool
-JsonTablePlanJoinNextRow(JsonTablePlanState *planstate)
-{
-
- /* Fetch row from left sibling. */
- if (!JsonTablePlanNextRow(planstate->left))
- {
- /*
- * Left sibling ran out of rows, so start fetching from the right
- * sibling.
- */
- if (!JsonTablePlanNextRow(planstate->right))
- {
- /* Right sibling ran out of rows too, so there are no more rows. */
- return false;
- }
- }
-
- return true;
-}
-
/*
* JsonTableFetchRow
* Prepare the next "current" row for upcoming GetValue calls.
@@ -4802,3 +4866,26 @@ JsonTableGetValue(TableFuncScanState *state, int colnum,
return result;
}
+
+/* Recursively reset planstate and its child nodes */
+static void
+JsonTableRescan(JsonTablePlanState *planstate)
+{
+ if (IsA(planstate->plan, JsonTablePathScan))
+ {
+ /* Reset plan iterator to the beginning of the item list */
+ JsonValueListInitIterator(&planstate->found, &planstate->iter);
+ planstate->current.value = PointerGetDatum(NULL);
+ planstate->current.isnull = true;
+ planstate->ordinal = 0;
+
+ if (planstate->nested)
+ JsonTableRescan(planstate->nested);
+ }
+ else if (IsA(planstate->plan, JsonTableSiblingJoin))
+ {
+ JsonTableRescan(planstate->left);
+ JsonTableRescan(planstate->right);
+ planstate->advanceRight = false;
+ }
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 819631781c0..b3aef797e7d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -12707,6 +12707,89 @@ get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
}
}
+/*
+ * json_table_plan_is_default - does this plan match the implicit default?
+ *
+ * When no PLAN clause is given, JSON_TABLE builds a default plan that joins
+ * every nested path to its parent with OUTER and every set of sibling paths
+ * with UNION (see transformJsonTableColumns()). Such a plan is fully implied
+ * by the NESTED COLUMNS structure, so we need not (and, to match the input,
+ * should not) print a PLAN clause for it; we only deparse a PLAN clause when
+ * the plan deviates from the default, i.e. uses an INNER or CROSS join
+ * somewhere. This follows the usual ruleutils convention of omitting a clause
+ * that merely restates the default (cf. get_json_expr_options() for ON
+ * EMPTY/ON ERROR, or the NULLS FIRST/LAST handling in get_rule_orderby()).
+ */
+static bool
+json_table_plan_is_default(JsonTablePlan *plan)
+{
+ if (IsA(plan, JsonTablePathScan))
+ {
+ JsonTablePathScan *scan = castNode(JsonTablePathScan, plan);
+
+ if (scan->child)
+ {
+ if (!scan->outerJoin)
+ return false; /* INNER is not the default */
+ return json_table_plan_is_default(scan->child);
+ }
+
+ return true;
+ }
+ else
+ {
+ JsonTableSiblingJoin *join = castNode(JsonTableSiblingJoin, plan);
+
+ if (join->cross)
+ return false; /* CROSS is not the default */
+ return json_table_plan_is_default(join->lplan) &&
+ json_table_plan_is_default(join->rplan);
+ }
+}
+
+/*
+ * get_json_table_plan - Parse back a JSON_TABLE plan
+ */
+static void
+get_json_table_plan(TableFunc *tf, JsonTablePlan *plan, deparse_context *context,
+ bool parenthesize)
+{
+ if (parenthesize)
+ appendStringInfoChar(context->buf, '(');
+
+ if (IsA(plan, JsonTablePathScan))
+ {
+ JsonTablePathScan *s = castNode(JsonTablePathScan, plan);
+
+ appendStringInfoString(context->buf, quote_identifier(s->path->name));
+
+ if (s->child)
+ {
+ appendStringInfoString(context->buf,
+ s->outerJoin ? " OUTER " : " INNER ");
+ get_json_table_plan(tf, s->child, context,
+ IsA(s->child, JsonTableSiblingJoin));
+ }
+ }
+ else if (IsA(plan, JsonTableSiblingJoin))
+ {
+ JsonTableSiblingJoin *j = (JsonTableSiblingJoin *) plan;
+
+ get_json_table_plan(tf, j->lplan, context,
+ IsA(j->lplan, JsonTableSiblingJoin) ||
+ castNode(JsonTablePathScan, j->lplan)->child);
+
+ appendStringInfoString(context->buf, j->cross ? " CROSS " : " UNION ");
+
+ get_json_table_plan(tf, j->rplan, context,
+ IsA(j->rplan, JsonTableSiblingJoin) ||
+ castNode(JsonTablePathScan, j->rplan)->child);
+ }
+
+ if (parenthesize)
+ appendStringInfoChar(context->buf, ')');
+}
+
/*
* get_json_table_columns - Parse back JSON_TABLE columns
*/
@@ -12716,6 +12799,7 @@ get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
bool showimplicit)
{
StringInfo buf = context->buf;
+ JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
ListCell *lc_colname;
ListCell *lc_coltype;
ListCell *lc_coltypmod;
@@ -12795,6 +12879,9 @@ get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
default_behavior = JSON_BEHAVIOR_NULL;
}
+ if (jexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+ default_behavior = JSON_BEHAVIOR_ERROR;
+
appendStringInfoString(buf, " PATH ");
get_json_path_spec(colexpr->path_spec, context, showimplicit);
@@ -12872,6 +12959,18 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
get_json_table_columns(tf, castNode(JsonTablePathScan, tf->plan), context,
showimplicit);
+ /*
+ * Deparse a PLAN clause only for a non-default plan; the default plan is
+ * implied by the NESTED COLUMNS structure (see
+ * json_table_plan_is_default).
+ */
+ if (root->child && !json_table_plan_is_default((JsonTablePlan *) root))
+ {
+ appendStringInfoChar(buf, ' ');
+ appendContextKeyword(context, "PLAN ", 0, 0, 0);
+ get_json_table_plan(tf, (JsonTablePlan *) root, context, true);
+ }
+
if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY_ARRAY)
get_json_behavior(jexpr->on_error, context, "ERROR");
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index bf54d39feb0..4c9d2ec7c09 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -124,5 +124,10 @@ extern JsonTablePath *makeJsonTablePath(Const *pathvalue, char *pathname);
extern JsonTablePathSpec *makeJsonTablePathSpec(char *string, char *name,
int string_location,
int name_location);
+extern Node *makeJsonTableDefaultPlan(JsonTablePlanJoinType join_type,
+ int location);
+extern Node *makeJsonTableSimplePlan(char *pathname, int location);
+extern Node *makeJsonTableJoinedPlan(JsonTablePlanJoinType type,
+ Node *plan1, Node *plan2, int location);
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e03556399ab..77f69f94607 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1982,6 +1982,48 @@ typedef struct JsonTablePathSpec
ParseLoc location; /* location of 'string' */
} JsonTablePathSpec;
+/*
+ * JsonTablePlanType -
+ * flags for JSON_TABLE plan node types representation
+ */
+typedef enum JsonTablePlanType
+{
+ JSTP_DEFAULT,
+ JSTP_SIMPLE,
+ JSTP_JOINED,
+} JsonTablePlanType;
+
+/*
+ * JsonTablePlanJoinType -
+ * JSON_TABLE join types for JSTP_JOINED plans
+ */
+typedef enum JsonTablePlanJoinType
+{
+ JSTP_JOIN_INNER = 0x01,
+ JSTP_JOIN_OUTER = 0x02,
+ JSTP_JOIN_CROSS = 0x04,
+ JSTP_JOIN_UNION = 0x08,
+} JsonTablePlanJoinType;
+
+/*
+ * JsonTablePlanSpec -
+ * untransformed representation of JSON_TABLE's PLAN clause
+ */
+typedef struct JsonTablePlanSpec
+{
+ NodeTag type;
+
+ JsonTablePlanType plan_type; /* plan type */
+ JsonTablePlanJoinType join_type; /* join type (for joined plan only) */
+ char *pathname; /* path name (for simple plan only) */
+
+ /* For joined plans */
+ struct JsonTablePlanSpec *plan1; /* first joined plan */
+ struct JsonTablePlanSpec *plan2; /* second joined plan */
+
+ ParseLoc location; /* token location, or -1 if unknown */
+} JsonTablePlanSpec;
+
/*
* JsonTable -
* untransformed representation of JSON_TABLE
@@ -1993,6 +2035,7 @@ typedef struct JsonTable
JsonTablePathSpec *pathspec; /* JSON path specification */
List *passing; /* list of PASSING clause arguments, if any */
List *columns; /* list of JsonTableColumn */
+ JsonTablePlanSpec *planspec; /* join plan, if specified */
JsonBehavior *on_error; /* ON ERROR behavior */
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 bb05aeebee4..cacef7d4151 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1926,6 +1926,7 @@ typedef struct JsonTablePathScan
/* Plan(s) for nested columns, if any. */
JsonTablePlan *child;
+ bool outerJoin; /* outer or inner join for nested columns? */
/*
* 0-based index in TableFunc.colvalexprs of the 1st and the last column
@@ -1947,6 +1948,7 @@ typedef struct JsonTableSiblingJoin
JsonTablePlan *lplan;
JsonTablePlan *rplan;
+ bool cross;
} JsonTableSiblingJoin;
/* ----------------
diff --git a/src/test/regress/expected/sqljson_jsontable.out b/src/test/regress/expected/sqljson_jsontable.out
index 4d500e7de2d..1530ef8afe0 100644
--- a/src/test/regress/expected/sqljson_jsontable.out
+++ b/src/test/regress/expected/sqljson_jsontable.out
@@ -790,6 +790,244 @@ SELECT * FROM JSON_TABLE(
ERROR: duplicate JSON_TABLE column or path name: a
LINE 10: NESTED PATH '$' AS a
^
+-- JSON_TABLE: nested paths and plans
+-- Path names are not required on the row pattern or the NESTED paths, even
+-- when a PLAN clause is present; a name is generated for any path left
+-- unnamed. The next two queries succeed.
+-- PLAN DEFAULT with an unnamed row pattern
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$'
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ foo
+-----
+
+(1 row)
+
+-- PLAN DEFAULT with an unnamed NESTED path
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS (
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ foo
+-----
+
+(1 row)
+
+-- An unnamed NESTED path under an explicit PLAN() is also accepted, but the
+-- generated name cannot be referenced by the plan, so it fails as a nested
+-- path not covered by the plan.
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS (
+ foo int PATH '$'
+ )
+ )
+ PLAN (path1)
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 4: NESTED PATH '$' COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path json_table_path_0 was not found.
+-- 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 specification
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p1 was not found.
+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 specification
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p1 was not found.
+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 clause
+LINE 12: PLAN (p0 UNION p1 UNION p11)
+ ^
+DETAIL: Expected INNER or OUTER.
+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 specification
+LINE 8: NESTED PATH '$' AS p2 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p2 was not found.
+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 specification
+LINE 5: NESTED PATH '$' AS p11 COLUMNS ( foo ...
+ ^
+DETAIL: PLAN clause for nested path p11 was not found.
+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 clause
+LINE 12: PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+ ^
+DETAIL: PLAN clause 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 specification
+LINE 6: NESTED PATH '$' AS p12 COLUMNS ( bar ...
+ ^
+DETAIL: PLAN clause for nested path p12 was not found.
+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 specification
+LINE 9: NESTED PATH '$' AS p21 COLUMNS ( baz ...
+ ^
+DETAIL: PLAN clause for nested path p21 was not found.
+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)
+
+-- Should fail (top-level plan must join the row pattern using INNER or
+-- OUTER, not a bare sibling CROSS)
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]'
+ 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 plan clause
+LINE 12: PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)...
+ ^
+DETAIL: Expected INNER or OUTER.
-- JSON_TABLE: plan execution
CREATE TEMP TABLE jsonb_table_test (js jsonb);
INSERT INTO jsonb_table_test
@@ -829,6 +1067,325 @@ from
4 | -1 | 2 | 2 | |
(11 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;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-- specific plan (p outer (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'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 $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(10 rows)
+
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'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 $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (cross, inner)
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+(9 rows)
+
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'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 $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'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)
+
-- PASSING arguments are passed to nested paths and their columns' paths
SELECT *
FROM
@@ -892,6 +1449,7 @@ SELECT * FROM
)
)
);
+CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
\sv jsonb_table_view_nested
CREATE OR REPLACE VIEW public.jsonb_table_view_nested AS
SELECT id,
@@ -929,7 +1487,102 @@ CREATE OR REPLACE VIEW public.jsonb_table_view_nested AS
)
)
)
+CREATE OR REPLACE VIEW public.jsonb_table_view AS
+ SELECT id,
+ "int",
+ text,
+ "char(4)",
+ bool,
+ "numeric",
+ domain,
+ js,
+ jb,
+ jst,
+ jsc,
+ jsv,
+ jsb,
+ jsbq,
+ aaa,
+ aaa1,
+ exists1,
+ exists2,
+ exists3,
+ js2,
+ jsb2w,
+ jsb2q,
+ ia,
+ ta,
+ jba,
+ a1,
+ b1,
+ a11,
+ a21,
+ a22
+ FROM JSON_TABLE(
+ 'null'::jsonb, '$[*]' AS json_table_path_1
+ PASSING
+ 1 + 2 AS a,
+ '"foo"'::json AS "b c"
+ COLUMNS (
+ id 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))))
+ );
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Table Function Scan on "json_table"
+ Output: "json_table".id, "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, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id 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 '$' WITHOUT WRAPPER KEEP QUOTES, jb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jst text FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsc character(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsv character varying(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsbq jsonb PATH '$' WITHOUT WRAPPER 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 '$' WITHOUT WRAPPER KEEP QUOTES, jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER KEEP QUOTES, jsb2q jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, ia integer[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, 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"'))))
+(3 rows)
+
+DROP VIEW jsonb_table_view;
DROP VIEW jsonb_table_view_nested;
+DROP DOMAIN jsonb_test_domain;
CREATE TABLE s (js jsonb);
INSERT INTO s VALUES
('{"a":{"za":[{"z1": [11,2222]},{"z21": [22, 234,2345]},{"z22": [32, 204,145]}]},"c": 3}'),
@@ -1168,7 +1821,7 @@ CREATE OR REPLACE VIEW public.json_table_view9 AS
FROM JSON_TABLE(
'"a"'::text, '$' AS json_table_path_0
COLUMNS (
- a text PATH '$'
+ a text PATH '$' NULL ON EMPTY
) ERROR ON ERROR
)
DROP VIEW json_table_view8, json_table_view9;
diff --git a/src/test/regress/sql/sqljson_jsontable.sql b/src/test/regress/sql/sqljson_jsontable.sql
index 41824094b96..d71d57e99b2 100644
--- a/src/test/regress/sql/sqljson_jsontable.sql
+++ b/src/test/regress/sql/sqljson_jsontable.sql
@@ -385,6 +385,187 @@ SELECT * FROM JSON_TABLE(
)
) jt;
+-- JSON_TABLE: nested paths and plans
+-- Path names are not required on the row pattern or the NESTED paths, even
+-- when a PLAN clause is present; a name is generated for any path left
+-- unnamed. The next two queries succeed.
+-- PLAN DEFAULT with an unnamed row pattern
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$'
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+-- PLAN DEFAULT with an unnamed NESTED path
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS (
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+-- An unnamed NESTED path under an explicit PLAN() is also accepted, but the
+-- generated name cannot be referenced by the plan, so it fails as a nested
+-- path not covered by the plan.
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS (
+ foo int PATH '$'
+ )
+ )
+ PLAN (path1)
+) 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;
+-- Should fail (top-level plan must join the row pattern using INNER or
+-- OUTER, not a bare sibling CROSS)
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]'
+ 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
@@ -414,6 +595,170 @@ from
)
) jt;
+-- 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;
+-- 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 $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner)
+ ) jt;
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'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 $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (cross, inner)
+ ) jt;
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'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 $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt;
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'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;
-- PASSING arguments are passed to nested paths and their columns' paths
SELECT *
@@ -459,8 +804,97 @@ SELECT * FROM
)
);
+CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
\sv jsonb_table_view_nested
+CREATE OR REPLACE VIEW public.jsonb_table_view AS
+ SELECT id,
+ "int",
+ text,
+ "char(4)",
+ bool,
+ "numeric",
+ domain,
+ js,
+ jb,
+ jst,
+ jsc,
+ jsv,
+ jsb,
+ jsbq,
+ aaa,
+ aaa1,
+ exists1,
+ exists2,
+ exists3,
+ js2,
+ jsb2w,
+ jsb2q,
+ ia,
+ ta,
+ jba,
+ a1,
+ b1,
+ a11,
+ a21,
+ a22
+ FROM JSON_TABLE(
+ 'null'::jsonb, '$[*]' AS json_table_path_1
+ PASSING
+ 1 + 2 AS a,
+ '"foo"'::json AS "b c"
+ COLUMNS (
+ id 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))))
+ );
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
+DROP VIEW jsonb_table_view;
DROP VIEW jsonb_table_view_nested;
+DROP DOMAIN jsonb_test_domain;
CREATE TABLE s (js jsonb);
INSERT INTO s VALUES
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ffb413ab612..f6e87d04b0e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1508,7 +1508,10 @@ JsonTablePathScan
JsonTablePathSpec
JsonTablePlan
JsonTablePlanRowSource
+JsonTablePlanSpec
JsonTablePlanState
+JsonTablePlanJoinType
+JsonTablePlanType
JsonTableSiblingJoin
JsonTokenType
JsonTransformStringValuesAction