JSON_TABLE PLAN clause

This patch adds the PLAN clauses for JSON_TABLE, which allow the user
to specify how data from nested paths are joined, allowing
considerable freedom in shaping the tabular output of JSON_TABLE.
PLAN DEFAULT allows the user to specify the global strategies when
dealing with sibling or child nested paths. This is often sufficient
to achieve the necessary goal, and is considerably simpler than the
full PLAN clause, which allows the user to specify the strategy to be
used for each named nested path.

Path names may be attached to the row pattern and to each NESTED path
using AS.  Unlike the SQL/JSON standard, which requires a name for every
NESTED path when a PLAN clause is present, PostgreSQL does not require
them and generates a name for any path left unnamed; a specific PLAN()
can only reference paths by name, so unnamed paths it must mention are
reported as not covered by the plan.

Author: Nikita Malakhov <n.malakhov@postgrespro.ru>
Co-authored-by: Nikita Glukhov <n.gluhov@postgrespro.ru>
Co-authored-by: Teodor Sigaev <teodor@sigaev.ru>
Co-authored-by: Oleg Bartunov <obartunov@gmail.com>
Co-authored-by: Alexander Korotkov <aekorotkov@gmail.com>
Co-authored-by: Andrew Dunstan <andrew@dunslane.net>
Co-authored-by: Amit Langote <amitlangote09@gmail.com>
Co-authored-by: Anton Melnikov <a.melnikov@postgrespro.ru>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Pavel Stehule <pavel.stehule@gmail.com>
Reviewed-by: Andrew Alsup <bluesbreaker@gmail.com>
Reviewed-by: Erik Rijkers <er@xs4all.nl>
Reviewed-by: Zhihong Yu <zyu@yugabyte.com>
Reviewed-by: Himanshu Upadhyaya <upadhyaya.himanshu@gmail.com>
Reviewed-by: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: Justin Pryzby <pryzby@telsasoft.com>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Vladlen Popolitov <v.popolitov@postgrespro.ru>
Discussion: https://postgr.es/m/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru
Discussion: https://postgr.es/m/20220616233130.rparivafipt6doj3@alap3.anarazel.de
Discussion: https://postgr.es/m/abd9b83b-aa66-f230-3d6d-734817f0995d%40postgresql.org
Discussion: https://postgr.es/m/CA+HiwqE4XTdfb1nW=Ojoy_tQSRhYt-q_kb6i5d4xcKyrLC1Nbg@mail.gmail.com
Discussion: https://postgr.es/m/CAN-LCVP7HXmGu-WcinsHvdKqMGEdv=1Y67H4U58F6Y=Q0M5GyQ@mail.gmail.com
This commit is contained in:
Alexander Korotkov 2026-07-09 14:37:31 +03:00
parent 2e6578292a
commit 86ab7f4c72
13 changed files with 2025 additions and 120 deletions

View file

@ -3663,6 +3663,11 @@ JSON_TABLE (
<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>
@ -3679,6 +3684,16 @@ where <replaceable class="parameter">json_table_column</replaceable> is:
| <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>
@ -3842,6 +3857,11 @@ where <replaceable class="parameter">json_table_column</replaceable> is:
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>
@ -3866,9 +3886,127 @@ where <replaceable class="parameter">json_table_column</replaceable> is:
<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>
@ -4082,5 +4220,39 @@ COLUMNS (
</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>

View file

@ -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

View file

@ -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

View file

@ -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 <list> 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 <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
@ -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

View file

@ -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));
}

View file

@ -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;
}
}

View file

@ -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");

View file

@ -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 */

View file

@ -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? */

View file

@ -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;
/* ----------------

View file

@ -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;

View file

@ -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

View file

@ -1508,7 +1508,10 @@ JsonTablePathScan
JsonTablePathSpec
JsonTablePlan
JsonTablePlanRowSource
JsonTablePlanSpec
JsonTablePlanState
JsonTablePlanJoinType
JsonTablePlanType
JsonTableSiblingJoin
JsonTokenType
JsonTransformStringValuesAction