1
0
mirror of https://github.com/postgres/postgres.git synced 2025-08-21 10:42:50 +03:00

JSON_TABLE: Add support for NESTED paths and columns

A NESTED path allows to extract data from nested levels of JSON
objects given by the parent path expression, which are projected as
columns specified using a nested COLUMNS clause, just like the parent
COLUMNS clause.  Rows comprised from a NESTED columns are "joined"
to the row comprised from the parent columns.  If a particular NESTED
path evaluates to 0 rows, then the nested COLUMNS will emit NULLs,
making it an OUTER join.

NESTED columns themselves may include NESTED paths to allow
extracting data from arbitrary nesting levels, which are likewise
joined against the rows at the parent level.

Multiple NESTED paths at a given level are called "sibling" paths
and their rows are combined by UNIONing them, that is, after being
joined against the parent row as described above.

Author: Nikita Glukhov <n.gluhov@postgrespro.ru>
Author: Teodor Sigaev <teodor@sigaev.ru>
Author: Oleg Bartunov <obartunov@gmail.com>
Author: Alexander Korotkov <aekorotkov@gmail.com>
Author: Andrew Dunstan <andrew@dunslane.net>
Author: Amit Langote <amitlangote09@gmail.com>
Author: Jian He <jian.universality@gmail.com>

Reviewers have included (in no particular order):

Andres Freund, Alexander Korotkov, Pavel Stehule, Andrew Alsup,
Erik Rijkers, Zihong Yu, Himanshu Upadhyaya, Daniel Gustafsson,
Justin Pryzby, Álvaro Herrera, Jian He

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
This commit is contained in:
Amit Langote
2024-04-08 15:58:58 +09:00
parent f6a2529920
commit bb766cde63
17 changed files with 1209 additions and 33 deletions

View File

@@ -755,7 +755,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
MINUTE_P MINVALUE MODE MONTH_P MOVE
NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO
NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NFC NFD NFKC NFKD NO
NONE NORMALIZE NORMALIZED
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
NULLS_P NUMERIC
@@ -884,8 +884,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* the same precedence as IDENT. This allows resolving conflicts in the
* json_predicate_type_constraint and json_key_uniqueness_constraint_opt
* productions (see comments there).
*
* Like the UNBOUNDED PRECEDING/FOLLOWING case, NESTED is assigned a lower
* precedence than PATH to fix ambiguity in the json_table production.
*/
%nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */
%nonassoc UNBOUNDED NESTED /* ideally would have same precedence as IDENT */
%nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH
%left Op OPERATOR /* multi-character ops and user-defined operators */
@@ -14270,6 +14273,35 @@ json_table_column_definition:
n->location = @1;
$$ = (Node *) n;
}
| NESTED path_opt Sconst
COLUMNS '(' json_table_column_definition_list ')'
{
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
n->pathspec = (JsonTablePathSpec *)
makeJsonTablePathSpec($3, NULL, @3, -1);
n->columns = $6;
n->location = @1;
$$ = (Node *) n;
}
| NESTED path_opt Sconst AS name
COLUMNS '(' json_table_column_definition_list ')'
{
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
n->pathspec = (JsonTablePathSpec *)
makeJsonTablePathSpec($3, $5, @3, @5);
n->columns = $8;
n->location = @1;
$$ = (Node *) n;
}
;
path_opt:
PATH
| /* EMPTY */
;
json_table_column_path_clause_opt:
@@ -17688,6 +17720,7 @@ unreserved_keyword:
| MOVE
| NAME_P
| NAMES
| NESTED
| NEW
| NEXT
| NFC
@@ -18304,6 +18337,7 @@ bare_label_keyword:
| NATIONAL
| NATURAL
| NCHAR
| NESTED
| NEW
| NEXT
| NFC

View File

@@ -44,16 +44,23 @@ static JsonTablePlan *transformJsonTableColumns(JsonTableParseContext *cxt,
List *columns,
List *passingArgs,
JsonTablePathSpec *pathspec);
static JsonTablePlan *transformJsonTableNestedColumns(JsonTableParseContext *cxt,
List *passingArgs,
List *columns);
static JsonFuncExpr *transformJsonTableColumn(JsonTableColumn *jtc,
Node *contextItemExpr,
List *passingArgs);
static bool isCompositeType(Oid typid);
static JsonTablePlan *makeJsonTablePathScan(JsonTablePathSpec *pathspec,
bool errorOnError);
bool errorOnError,
int colMin, int colMax,
JsonTablePlan *childplan);
static void CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
List *columns);
static bool LookupPathOrColumnName(JsonTableParseContext *cxt, char *name);
static char *generateJsonTablePathName(JsonTableParseContext *cxt);
static JsonTablePlan *makeJsonTableSiblingJoin(JsonTablePlan *lplan,
JsonTablePlan *rplan);
/*
* transformJsonTable -
@@ -172,13 +179,32 @@ CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
{
JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc1));
if (LookupPathOrColumnName(cxt, jtc->name))
ereport(ERROR,
errcode(ERRCODE_DUPLICATE_ALIAS),
errmsg("duplicate JSON_TABLE column or path name: %s",
jtc->name),
parser_errposition(cxt->pstate, jtc->location));
cxt->pathNames = lappend(cxt->pathNames, jtc->name);
if (jtc->coltype == JTC_NESTED)
{
if (jtc->pathspec->name)
{
if (LookupPathOrColumnName(cxt, jtc->pathspec->name))
ereport(ERROR,
errcode(ERRCODE_DUPLICATE_ALIAS),
errmsg("duplicate JSON_TABLE column or path name: %s",
jtc->pathspec->name),
parser_errposition(cxt->pstate,
jtc->pathspec->name_location));
cxt->pathNames = lappend(cxt->pathNames, jtc->pathspec->name);
}
CheckDuplicateColumnOrPathNames(cxt, jtc->columns);
}
else
{
if (LookupPathOrColumnName(cxt, jtc->name))
ereport(ERROR,
errcode(ERRCODE_DUPLICATE_ALIAS),
errmsg("duplicate JSON_TABLE column or path name: %s",
jtc->name),
parser_errposition(cxt->pstate, jtc->location));
cxt->pathNames = lappend(cxt->pathNames, jtc->name);
}
}
}
@@ -234,6 +260,12 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
bool errorOnError = jt->on_error &&
jt->on_error->btype == JSON_BEHAVIOR_ERROR;
Oid contextItemTypid = exprType(tf->docexpr);
int colMin,
colMax;
JsonTablePlan *childplan;
/* Start of column range */
colMin = list_length(tf->colvalexprs);
foreach(col, columns)
{
@@ -243,9 +275,12 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
Oid typcoll = InvalidOid;
Node *colexpr;
Assert(rawc->name);
tf->colnames = lappend(tf->colnames,
makeString(pstrdup(rawc->name)));
if (rawc->coltype != JTC_NESTED)
{
Assert(rawc->name);
tf->colnames = lappend(tf->colnames,
makeString(pstrdup(rawc->name)));
}
/*
* Determine the type and typmod for the new column. FOR ORDINALITY
@@ -303,6 +338,9 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
break;
}
case JTC_NESTED:
continue;
default:
elog(ERROR, "unknown JSON_TABLE column type: %d", (int) rawc->coltype);
break;
@@ -314,7 +352,21 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
}
return makeJsonTablePathScan(pathspec, errorOnError);
/* 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);
}
/*
@@ -397,10 +449,58 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
}
/*
* 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.
*/
static JsonTablePlan *
makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError)
transformJsonTableNestedColumns(JsonTableParseContext *cxt,
List *passingArgs,
List *columns)
{
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;
}
return plan;
}
/*
* Create a JsonTablePlan for given path and ON ERROR behavior.
*
* colMin and colMin 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,
int colMin, int colMax,
JsonTablePlan *childplan)
{
JsonTablePathScan *scan = makeNode(JsonTablePathScan);
char *pathstring;
@@ -417,5 +517,29 @@ makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError)
scan->path = makeJsonTablePath(value, pathspec->name);
scan->errorOnError = errorOnError;
scan->child = childplan;
scan->colMin = colMin;
scan->colMax = colMax;
return (JsonTablePlan *) scan;
}
/*
* Create a JsonTablePlan that will perform a join of the rows coming from
* 'lplan' and 'rplan'.
*
* The default way of "joining" the rows is to perform a UNION between the
* sets of rows from 'lplan' and 'rplan'.
*/
static JsonTablePlan *
makeJsonTableSiblingJoin(JsonTablePlan *lplan, JsonTablePlan *rplan)
{
JsonTableSiblingJoin *join = makeNode(JsonTableSiblingJoin);
join->plan.type = T_JsonTableSiblingJoin;
join->lplan = lplan;
join->rplan = rplan;
return (JsonTablePlan *) join;
}