<replaceable>context_item</replaceable>, <replaceable>path_expression</replaceable> <optional> AS <replaceable>json_path_name</replaceable> </optional> <optional> PASSING { <replaceable>value</replaceable> AS <replaceable>varname</replaceable> } <optional>, ...</optional> </optional>
COLUMNS ( <replaceable class="parameter">json_table_column</replaceable> <optional>, ...</optional> )
<optional> { <literal>ERROR</literal> | <literal>EMPTY</literal> <optional>ARRAY</optional>} <literal>ON ERROR</literal> </optional>
+ <optional>
+ PLAN ( <replaceable class="parameter">json_table_plan</replaceable> ) |
+ PLAN DEFAULT ( { INNER | OUTER } <optional> , { CROSS | UNION } </optional>
+ | { CROSS | UNION } <optional> , { INNER | OUTER } </optional> )
+ </optional>
)
<phrase>
| <replaceable>name</replaceable> <replaceable>type</replaceable> EXISTS <optional> PATH <replaceable>path_expression</replaceable> </optional>
<optional> { ERROR | TRUE | FALSE | UNKNOWN } ON ERROR </optional>
| NESTED <optional> PATH </optional> <replaceable>path_expression</replaceable> <optional> AS <replaceable>json_path_name</replaceable> </optional> COLUMNS ( <replaceable>json_table_column</replaceable> <optional>, ...</optional> )
+<phrase>
+<replaceable>json_table_plan</replaceable> is:
+</phrase>
+ <replaceable>json_path_name</replaceable> <optional> { OUTER | INNER } <replaceable>json_table_plan_primary</replaceable> </optional>
+ | <replaceable>json_table_plan_primary</replaceable> { UNION <replaceable>json_table_plan_primary</replaceable> } <optional>...</optional>
+ | <replaceable>json_table_plan_primary</replaceable> { CROSS <replaceable>json_table_plan_primary</replaceable> } <optional>...</optional>
+<phrase>
+<replaceable>json_table_plan_primary</replaceable> is:
+</phrase>
+ <replaceable>json_path_name</replaceable> | ( <replaceable>json_table_plan</replaceable> )
</synopsis>
<para>
in a single function invocation rather than chaining several
<function>JSON_TABLE</function> expressions in an SQL statement.
</para>
+
+ <para>
+ You can use the <literal>PLAN</literal> clause to define how
+ to join the columns returned by <literal>NESTED PATH</literal> clauses.
+ </para>
</listitem>
</varlistentry>
</variablelist>
<para>
The optional <replaceable>json_path_name</replaceable> serves as an
- identifier of the provided <replaceable>path_expression</replaceable>.
- The name must be unique and distinct from the column names.
+ identifier of the provided <replaceable>json_path_specification</replaceable>.
+ The path name must be unique and distinct from the column names. Each
+ path name can appear in the <literal>PLAN</literal> clause only once.
+ </para>
+ <para>
+ The SQL/JSON standard requires an explicit path name for every
+ <literal>NESTED PATH</literal> when a <literal>PLAN</literal> or
+ <literal>PLAN DEFAULT</literal> clause is present, while the row pattern
+ path name always remains optional. As an extension,
+ <productname>PostgreSQL</productname> does not require any path name: a
+ name is generated for any path left unnamed. Note, however, that a
+ specific <literal>PLAN</literal> (<replaceable>json_table_plan</replaceable>)
+ can only refer to paths by name, so every path that such a plan must
+ mention has to be given a name explicitly.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>PLAN</literal> ( <replaceable>json_table_plan</replaceable> )
+ </term>
+ <listitem>
+
+ <para>
+ Defines how to join the data returned by <literal>NESTED PATH</literal>
+ clauses to the constructed view.
+ </para>
+ <para>
+ To join columns with parent/child relationship, you can use:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal>INNER</literal>
+ </term>
+ <listitem>
+
+ <para>
+ Use <literal>INNER JOIN</literal>, so that the parent row
+ is omitted from the output if it does not have any child rows
+ after joining the data returned by <literal>NESTED PATH</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>OUTER</literal>
+ </term>
+ <listitem>
+
+ <para>
+ Use <literal>LEFT OUTER JOIN</literal>, 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 <literal>NESTED PATH</literal>, with NULL values
+ inserted into the child columns if the corresponding
+ values are missing.
+ </para>
+ <para>
+ This is the default option for joining columns with parent/child relationship.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ To join sibling columns, you can use:
+ </para>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <literal>UNION</literal>
+ </term>
+ <listitem>
+
+ <para>
+ Generate one row for each value produced by each of the sibling
+ columns. The columns from the other siblings are set to null.
+ </para>
+ <para>
+ This is the default option for joining sibling columns.
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>
+ <literal>CROSS</literal>
+ </term>
+ <listitem>
+
+ <para>
+ Generate one row for each combination of values from the sibling columns.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>PLAN DEFAULT</literal> ( <literal><replaceable>OUTER | INNER</replaceable> <optional>, <replaceable>UNION | CROSS</replaceable> </optional></literal> )
+ </term>
+ <listitem>
+ <para>
+ The terms can also be specified in reverse order. The
+ <literal>INNER</literal> or <literal>OUTER</literal> option defines the
+ joining plan for parent/child columns, while <literal>UNION</literal> or
+ <literal>CROSS</literal> affects joins of sibling columns. This form
+ of <literal>PLAN</literal> overrides the default plan for
+ all columns at once.
+ </para>
+ <para>
+ <literal>PLAN DEFAULT</literal> is simpler than specifying a complete
+ <literal>PLAN</literal>, and is often all that is required to get the desired
+ output.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term>
{ <literal>ERROR</literal> | <literal>EMPTY</literal> } <literal>ON ERROR</literal>
</screen>
</para>
+
+ <para>
+ Find a director that has done films in two different genres:
+<screen>
+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)
+</screen>
+ </para>
+
</sect2>
</sect1>
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
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
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
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 <list> json_name_and_value_list
json_value_expr_list
json_array_aggregate_order_by_clause_opt
%type <ival> 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 <boolean> json_key_uniqueness_constraint_opt
json_object_constructor_null_clause_opt
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
')'
{
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;
}
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;
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;
{ $$ = 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
} 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);
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 -
TableFunc *tf;
JsonFuncExpr *jfe;
JsonExpr *je;
+ JsonTablePlanSpec *plan = jt->planspec;
JsonTablePathSpec *rootPathSpec = jt->pathspec;
bool is_lateral;
JsonTableParseContext cxt = {pstate};
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);
*/
cxt.jt = jt;
cxt.tf = tf;
- tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns,
+ tf->plan = (Node *) transformJsonTableColumns(&cxt, plan, jt->columns,
jt->passing,
rootPathSpec);
* 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));
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
param->typeMod = -1;
jfe = transformJsonTableColumn(rawc, (Node *) param,
- passingArgs);
+ passingArgs, errorOnError);
colexpr = transformExpr(pstate, (Node *) jfe,
EXPR_KIND_FROM_FUNCTION);
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);
}
/*
*/
static JsonFuncExpr *
transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
- List *passingArgs)
+ List *passingArgs, bool errorOnError)
{
Node *pathspec;
JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
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;
return jfexpr;
}
+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->pathspec->name &&
+ !strcmp(jtc->pathspec->name, pathname))
+ return jtc;
+ }
+
+ return NULL;
+}
+
/*
* Recursively transform nested columns and create child plan(s) that will be
* used to evaluate their row patterns.
+ *
+ * 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,
- List *passingArgs,
- List *columns)
+ JsonTablePlanSpec *planspec,
+ List *columns,
+ List *passingArgs)
{
- JsonTablePlan *plan = NULL;
- ListCell *lc;
+ JsonTableColumn *jtc = NULL;
- /*
- * 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)
+ if (!planspec || planspec->plan_type == JSTP_DEFAULT)
{
- JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
- JsonTablePlan *nested;
+ /* unspecified or default plan */
+ JsonTablePlan *plan = NULL;
+ ListCell *lc;
+ bool cross = planspec && (planspec->join_type & JSTP_JOIN_CROSS);
- if (jtc->coltype != JTC_NESTED)
- continue;
+ /*
+ * 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 (jtc->pathspec->name == NULL)
- jtc->pathspec->name = generateJsonTablePathName(cxt);
+ if (col->coltype != JTC_NESTED)
+ continue;
+
+ if (col->pathspec->name == NULL)
+ {
+ col->pathspec->name = generateJsonTablePathName(cxt);
+ }
- nested = transformJsonTableColumns(cxt, jtc->columns, passingArgs,
- jtc->pathspec);
+ nested = transformJsonTableColumns(cxt, planspec, col->columns,
+ passingArgs,
+ col->pathspec);
- if (plan)
- plan = makeJsonTableSiblingJoin(plan, nested);
+ /* 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
- plan = nested;
+ {
+ 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);
- return plan;
+ 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 a JsonTablePlan for given path and ON ERROR behavior.
+ * 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 colMin give the range of columns computed by this scan in the
+ * 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)
{
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;
}
* 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));
+}
/* 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 */
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 =
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",
{
JsonTableSiblingJoin *join = (JsonTableSiblingJoin *) plan;
+ planstate->cross = join->cross;
+
planstate->left = JsonTableInitPlan(cxt, join->lplan, parentstate,
args, mcxt);
planstate->right = JsonTableInitPlan(cxt, join->rplan, parentstate,
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);
}
/*
static bool
JsonTablePlanNextRow(JsonTablePlanState *planstate)
{
- if (IsA(planstate->plan, JsonTablePathScan))
- return JsonTablePlanScanNextRow(planstate);
- else if (IsA(planstate->plan, JsonTableSiblingJoin))
- return JsonTablePlanJoinNextRow(planstate);
+ if (IsA(planstate->plan, JsonTableSiblingJoin))
+ {
+ if (planstate->advanceRight)
+ {
+ /* fetch next inner row */
+ if (JsonTablePlanNextRow(planstate->right))
+ return true;
+
+ /* 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
- elog(ERROR, "invalid JsonTablePlan %d", (int) planstate->plan->type);
+ {
+ /* 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;
- Assert(false);
- /* Appease compiler */
- return false;
+ /*
+ * 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;
}
/*
{
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.
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;
+ }
+}
}
}
+/*
+ * 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
*/
bool showimplicit)
{
StringInfo buf = context->buf;
+ JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
ListCell *lc_colname;
ListCell *lc_coltype;
ListCell *lc_coltypmod;
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);
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");
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 */
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
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? */
/* 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
JsonTablePlan *lplan;
JsonTablePlan *rplan;
+ bool cross;
} JsonTableSiblingJoin;
/* ----------------
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
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
)
)
);
+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,
)
)
)
+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}'),
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;
)
) 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
)
) 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 *
)
);
+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
JsonTablePathSpec
JsonTablePlan
JsonTablePlanRowSource
+JsonTablePlanSpec
JsonTablePlanState
+JsonTablePlanJoinType
+JsonTablePlanType
JsonTableSiblingJoin
JsonTokenType
JsonTransformStringValuesAction