Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * parse_jsontable.c
4 : : * parsing of JSON_TABLE
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/parser/parse_jsontable.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : :
16 : : #include "postgres.h"
17 : :
18 : : #include "catalog/pg_type.h"
19 : : #include "nodes/makefuncs.h"
20 : : #include "nodes/nodeFuncs.h"
21 : : #include "optimizer/optimizer.h"
22 : : #include "parser/parse_clause.h"
23 : : #include "parser/parse_collate.h"
24 : : #include "parser/parse_expr.h"
25 : : #include "parser/parse_relation.h"
26 : : #include "parser/parse_type.h"
27 : : #include "utils/fmgrprotos.h"
28 : : #include "utils/json.h"
29 : : #include "utils/lsyscache.h"
30 : :
31 : : /* Context for transformJsonTableColumns() */
32 : : typedef struct JsonTableParseContext
33 : : {
34 : : ParseState *pstate;
35 : : JsonTable *jt;
36 : : TableFunc *tf;
37 : : List *pathNames; /* list of all path and columns names */
38 : : int pathNameId; /* path name id counter */
39 : : } JsonTableParseContext;
40 : :
41 : : static JsonTablePlan *transformJsonTableColumns(JsonTableParseContext *cxt,
42 : : JsonTablePlanSpec *planspec,
43 : : List *columns,
44 : : List *passingArgs,
45 : : JsonTablePathSpec *pathspec);
46 : : static JsonTablePlan *transformJsonTableNestedColumns(JsonTableParseContext *cxt,
47 : : JsonTablePlanSpec *plan,
48 : : List *passingArgs,
49 : : List *columns);
50 : : static JsonFuncExpr *transformJsonTableColumn(JsonTableColumn *jtc,
51 : : Node *contextItemExpr,
52 : : List *passingArgs);
53 : : static bool isCompositeType(Oid typid);
54 : : static JsonTablePlan *makeJsonTablePathScan(JsonTableParseContext *cxt,
55 : : JsonTablePathSpec *pathspec,
56 : : JsonTablePlanSpec *planspec,
57 : : bool errorOnError,
58 : : int colMin, int colMax,
59 : : JsonTablePlan *childplan);
60 : : static void CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
61 : : List *columns);
62 : : static bool LookupPathOrColumnName(JsonTableParseContext *cxt, char *name);
63 : : static char *generateJsonTablePathName(JsonTableParseContext *cxt);
64 : : static void validateJsonTableChildPlan(JsonTableParseContext *cxt,
65 : : JsonTablePlanSpec *plan,
66 : : List *columns);
67 : : static JsonTablePlan *makeJsonTableSiblingJoin(bool cross,
68 : : JsonTablePlan *lplan,
69 : : JsonTablePlan *rplan);
70 : : static void
71 : : appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingArgs);
72 : :
73 : : /*
74 : : * transformJsonTable -
75 : : * Transform a raw JsonTable into TableFunc
76 : : *
77 : : * Mainly, this transforms the JSON_TABLE() document-generating expression
78 : : * (jt->context_item) and the column-generating expressions (jt->columns) to
79 : : * populate TableFunc.docexpr and TableFunc.colvalexprs, respectively. Also,
80 : : * the PASSING values (jt->passing) are transformed and added into
81 : : * TableFunc.passingvalexprs.
82 : : */
83 : : ParseNamespaceItem *
84 : 586 : transformJsonTable(ParseState *pstate, JsonTable *jt)
85 : : {
86 : : TableFunc *tf;
87 : : JsonFuncExpr *jfe;
88 : : JsonExpr *je;
89 : 586 : JsonTablePlanSpec *plan = jt->planspec;
90 : 586 : JsonTablePathSpec *rootPathSpec = jt->pathspec;
91 : : bool is_lateral;
92 : 586 : JsonTableParseContext cxt = {pstate};
93 : :
94 : : Assert(IsA(rootPathSpec->string, A_Const) &&
95 : : castNode(A_Const, rootPathSpec->string)->val.node.type == T_String);
96 : :
97 [ + + ]: 586 : if (jt->on_error &&
98 [ + + ]: 51 : jt->on_error->btype != JSON_BEHAVIOR_ERROR &&
99 [ + - ]: 26 : jt->on_error->btype != JSON_BEHAVIOR_EMPTY &&
100 [ + + ]: 26 : jt->on_error->btype != JSON_BEHAVIOR_EMPTY_ARRAY)
101 [ + - ]: 12 : ereport(ERROR,
102 : : errcode(ERRCODE_SYNTAX_ERROR),
103 : : errmsg("invalid %s behavior", "ON ERROR"),
104 : : errdetail("Only EMPTY [ ARRAY ] or ERROR is allowed in the top-level ON ERROR clause."),
105 : : parser_errposition(pstate, jt->on_error->location));
106 : :
107 : 574 : cxt.pathNameId = 0;
108 : :
109 : : /*
110 : : * Collect the user-supplied path and column names, checking that they are
111 : : * distinct. If the row pattern path has an explicit name, it shares this
112 : : * namespace, so seed the list with it.
113 : : */
114 [ + + ]: 574 : if (rootPathSpec->name != NULL)
115 : 160 : cxt.pathNames = list_make1(rootPathSpec->name);
116 : 574 : CheckDuplicateColumnOrPathNames(&cxt, jt->columns);
117 : :
118 : : /*
119 : : * Generate a name for the row pattern path if it was not given one. Path
120 : : * names are optional for every path, including when a PLAN clause is
121 : : * present; a specific PLAN() can only reference named paths, so an
122 : : * unnamed path that the plan must mention is caught later as a path name
123 : : * mismatch or a path not covered by the plan. We generate the name only
124 : : * after collecting the user-supplied names above, so that it cannot
125 : : * collide with any of them.
126 : : */
127 [ + + ]: 554 : if (rootPathSpec->name == NULL)
128 : 402 : rootPathSpec->name = generateJsonTablePathName(&cxt);
129 : :
130 : : /*
131 : : * We make lateral_only names of this level visible, whether or not the
132 : : * RangeTableFunc is explicitly marked LATERAL. This is needed for SQL
133 : : * spec compliance and seems useful on convenience grounds for all
134 : : * functions in FROM.
135 : : *
136 : : * (LATERAL can't nest within a single pstate level, so we don't need
137 : : * save/restore logic here.)
138 : : */
139 : : Assert(!pstate->p_lateral_active);
140 : 554 : pstate->p_lateral_active = true;
141 : :
142 : 554 : tf = makeNode(TableFunc);
143 : 554 : tf->functype = TFT_JSON_TABLE;
144 : :
145 : : /*
146 : : * Transform JsonFuncExpr representing the top JSON_TABLE context_item and
147 : : * pathspec into a dummy JSON_TABLE_OP JsonExpr.
148 : : *
149 : : * The raw PASSING arguments are transformed here and again for each
150 : : * column that has a path (see transformJsonTableColumn()). Since
151 : : * transformExpr() modifies some raw nodes in place, such as replacing a
152 : : * SubLink's subselect with its Query, each transformation must be given
153 : : * its own copy, so that jt->passing stays untouched.
154 : : */
155 : 554 : jfe = makeNode(JsonFuncExpr);
156 : 554 : jfe->op = JSON_TABLE_OP;
157 : 554 : jfe->context_item = jt->context_item;
158 : 554 : jfe->pathspec = (Node *) rootPathSpec->string;
159 : 554 : jfe->passing = copyObject(jt->passing);
160 : 554 : jfe->on_empty = NULL;
161 : 554 : jfe->on_error = jt->on_error;
162 : 554 : jfe->location = jt->location;
163 : 554 : tf->docexpr = transformExpr(pstate, (Node *) jfe, EXPR_KIND_FROM_FUNCTION);
164 : :
165 : : /*
166 : : * Assign collations now, because assign_query_collations() doesn't look
167 : : * into the range table. This must happen before the PASSING arguments
168 : : * are copied into tf->passingvalexprs below, so that the copies carry the
169 : : * collations too.
170 : : */
171 : 554 : assign_expr_collations(pstate, tf->docexpr);
172 : :
173 : : /*
174 : : * Create a JsonTablePlan that will generate row pattern that becomes
175 : : * source data for JSON path expressions in jt->columns. This also adds
176 : : * the columns' transformed JsonExpr nodes into tf->colvalexprs.
177 : : */
178 : 554 : cxt.jt = jt;
179 : 554 : cxt.tf = tf;
180 : 554 : tf->plan = (Node *) transformJsonTableColumns(&cxt, plan, jt->columns,
181 : : jt->passing,
182 : : rootPathSpec);
183 : :
184 : : /*
185 : : * Copy the transformed PASSING arguments into the TableFunc node, because
186 : : * they are evaluated separately from the JsonExpr that we just put in
187 : : * TableFunc.docexpr. JsonExpr.passing_values is still kept around for
188 : : * get_json_table().
189 : : */
190 : 486 : je = (JsonExpr *) tf->docexpr;
191 : 486 : tf->passingvalexprs = copyObject(je->passing_values);
192 : :
193 : 486 : tf->ordinalitycol = -1; /* undefine ordinality column number */
194 : 486 : tf->location = jt->location;
195 : :
196 : 486 : pstate->p_lateral_active = false;
197 : :
198 : : /*
199 : : * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
200 : : * there are any lateral cross-references in it.
201 : : */
202 [ + - + + ]: 486 : is_lateral = jt->lateral || contain_vars_of_level((Node *) tf, 0);
203 : :
204 : 486 : return addRangeTableEntryForTableFunc(pstate,
205 : : tf, jt->alias, is_lateral, true);
206 : : }
207 : :
208 : : /*
209 : : * Check if a column / path name is duplicated in the given shared list of
210 : : * names.
211 : : */
212 : : static void
213 : 1146 : CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
214 : : List *columns)
215 : : {
216 : : ListCell *lc1;
217 : :
218 [ + - + + : 3018 : foreach(lc1, columns)
+ + ]
219 : : {
220 : 1896 : JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc1));
221 : :
222 [ + + ]: 1896 : if (jtc->coltype == JTC_NESTED)
223 : : {
224 [ + + ]: 584 : if (jtc->pathspec->name)
225 : : {
226 [ + + ]: 466 : if (LookupPathOrColumnName(cxt, jtc->pathspec->name))
227 [ + - ]: 12 : ereport(ERROR,
228 : : errcode(ERRCODE_DUPLICATE_ALIAS),
229 : : errmsg("duplicate JSON_TABLE column or path name: %s",
230 : : jtc->pathspec->name),
231 : : parser_errposition(cxt->pstate,
232 : : jtc->pathspec->name_location));
233 : 454 : cxt->pathNames = lappend(cxt->pathNames, jtc->pathspec->name);
234 : : }
235 : :
236 : 572 : CheckDuplicateColumnOrPathNames(cxt, jtc->columns);
237 : : }
238 : : else
239 : : {
240 [ + + ]: 1312 : if (LookupPathOrColumnName(cxt, jtc->name))
241 [ + - ]: 8 : ereport(ERROR,
242 : : errcode(ERRCODE_DUPLICATE_ALIAS),
243 : : errmsg("duplicate JSON_TABLE column or path name: %s",
244 : : jtc->name),
245 : : parser_errposition(cxt->pstate, jtc->location));
246 : 1304 : cxt->pathNames = lappend(cxt->pathNames, jtc->name);
247 : : }
248 : : }
249 : 1122 : }
250 : :
251 : : /*
252 : : * Lookup a column/path name in the given name list, returning true if already
253 : : * there.
254 : : */
255 : : static bool
256 : 2302 : LookupPathOrColumnName(JsonTableParseContext *cxt, char *name)
257 : : {
258 : : ListCell *lc;
259 : :
260 [ + + + + : 10906 : foreach(lc, cxt->pathNames)
+ + ]
261 : : {
262 [ + + ]: 8632 : if (strcmp(name, (const char *) lfirst(lc)) == 0)
263 : 28 : return true;
264 : : }
265 : :
266 : 2274 : return false;
267 : : }
268 : :
269 : : /* Generate a new unique JSON_TABLE path name. */
270 : : static char *
271 : 516 : generateJsonTablePathName(JsonTableParseContext *cxt)
272 : : {
273 : : char namebuf[32];
274 : : char *name;
275 : :
276 : : /*
277 : : * Bump the counter until we produce a name that is not already used as a
278 : : * path or column name. Otherwise a generated name could coincide with a
279 : : * user-supplied one, which would confuse PLAN clause matching and could
280 : : * silently drop a column.
281 : : */
282 : : do
283 : : {
284 : 524 : snprintf(namebuf, sizeof(namebuf), "json_table_path_%d",
285 : 524 : cxt->pathNameId++);
286 [ + + ]: 524 : } while (LookupPathOrColumnName(cxt, namebuf));
287 : :
288 : 516 : name = pstrdup(namebuf);
289 : 516 : cxt->pathNames = lappend(cxt->pathNames, name);
290 : :
291 : 516 : return name;
292 : : }
293 : :
294 : : /*
295 : : * Create a JsonTablePlan that will supply the source row for 'columns'
296 : : * using 'pathspec' and append the columns' transformed JsonExpr nodes and
297 : : * their type/collation information to cxt->tf.
298 : : */
299 : : static JsonTablePlan *
300 : 930 : transformJsonTableColumns(JsonTableParseContext *cxt,
301 : : JsonTablePlanSpec *planspec,
302 : : List *columns,
303 : : List *passingArgs,
304 : : JsonTablePathSpec *pathspec)
305 : : {
306 : 930 : JsonTable *jt = cxt->jt;
307 : 930 : TableFunc *tf = cxt->tf;
308 : : JsonTablePathScan *scan;
309 : : JsonTablePlanSpec *childPlanSpec;
310 [ + + ]: 1236 : bool defaultPlan = planspec == NULL ||
311 [ + + ]: 306 : planspec->plan_type == JSTP_DEFAULT;
312 [ + + ]: 969 : bool errorOnError = jt->on_error &&
313 [ + + ]: 39 : jt->on_error->btype == JSON_BEHAVIOR_ERROR;
314 : : int colMin,
315 : : colMax;
316 : 930 : JsonTablePlan *childplan = NULL;
317 : :
318 : : /* Start of column range */
319 : 930 : colMin = list_length(tf->colvalexprs);
320 : :
321 [ + + ]: 930 : if (defaultPlan)
322 : 696 : childPlanSpec = planspec;
323 : : else
324 : : {
325 : : /* validate parent and child plans */
326 : : JsonTablePlanSpec *parentPlanSpec;
327 : :
328 [ + + ]: 234 : if (planspec->plan_type == JSTP_JOINED)
329 : : {
330 [ + + ]: 115 : if (planspec->join_type != JSTP_JOIN_INNER &&
331 [ + + ]: 81 : planspec->join_type != JSTP_JOIN_OUTER)
332 [ + - ]: 8 : ereport(ERROR,
333 : : (errcode(ERRCODE_SYNTAX_ERROR),
334 : : errmsg("invalid JSON_TABLE plan clause"),
335 : : errdetail("Expected INNER or OUTER."),
336 : : parser_errposition(cxt->pstate, planspec->location)));
337 : :
338 : 107 : parentPlanSpec = planspec->plan1;
339 : 107 : childPlanSpec = planspec->plan2;
340 : :
341 : : Assert(parentPlanSpec->plan_type != JSTP_JOINED);
342 : : Assert(parentPlanSpec->pathname);
343 : : }
344 : : else
345 : : {
346 : 119 : parentPlanSpec = planspec;
347 : 119 : childPlanSpec = NULL;
348 : : }
349 : :
350 [ + + ]: 226 : if (strcmp(parentPlanSpec->pathname, pathspec->name) != 0)
351 [ + - ]: 4 : ereport(ERROR,
352 : : (errcode(ERRCODE_SYNTAX_ERROR),
353 : : errmsg("invalid JSON_TABLE plan"),
354 : : errdetail("PATH name mismatch: expected %s but %s is given.",
355 : : pathspec->name, parentPlanSpec->pathname),
356 : : parser_errposition(cxt->pstate, planspec->location)));
357 : :
358 : 222 : validateJsonTableChildPlan(cxt, childPlanSpec, columns);
359 : : }
360 : :
361 : 882 : appendJsonTableColumns(cxt, columns, passingArgs);
362 : :
363 : : /* End of column range. */
364 [ + + ]: 862 : if (list_length(tf->colvalexprs) == colMin)
365 : : {
366 : : /* No columns in this Scan beside the nested ones. */
367 : 116 : colMax = colMin = -1;
368 : : }
369 : : else
370 : 746 : colMax = list_length(tf->colvalexprs) - 1;
371 : :
372 [ + + + + ]: 862 : if (childPlanSpec || defaultPlan)
373 : : {
374 : : /* transform recursively nested columns */
375 : 763 : childplan = transformJsonTableNestedColumns(cxt, childPlanSpec,
376 : : columns, passingArgs);
377 : : }
378 : :
379 : : /* transform only non-nested columns */
380 : 850 : scan = (JsonTablePathScan *) makeJsonTablePathScan(cxt, pathspec,
381 : : planspec,
382 : : errorOnError,
383 : : colMin,
384 : : colMax,
385 : : childplan);
386 : :
387 : 850 : return (JsonTablePlan *) scan;
388 : : }
389 : :
390 : : /* Append transformed non-nested JSON_TABLE columns to the TableFunc node */
391 : : static void
392 : 882 : appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingArgs)
393 : : {
394 : : ListCell *col;
395 : 882 : ParseState *pstate = cxt->pstate;
396 : 882 : TableFunc *tf = cxt->tf;
397 : 882 : bool ordinality_found = false;
398 : 882 : Oid contextItemTypid = exprType(tf->docexpr);
399 : :
400 [ + - + + : 2406 : foreach(col, columns)
+ + ]
401 : : {
402 : 1544 : JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col));
403 : : Oid typid;
404 : : int32 typmod;
405 : 1544 : Oid typcoll = InvalidOid;
406 : : Node *colexpr;
407 : :
408 [ + + ]: 1544 : if (rawc->name)
409 : 1160 : tf->colnames = lappend(tf->colnames,
410 : 1160 : makeString(pstrdup(rawc->name)));
411 : :
412 : : /*
413 : : * Determine the type and typmod for the new column. FOR ORDINALITY
414 : : * columns are INTEGER by standard; the others are user-specified.
415 : : */
416 [ + + + + : 1544 : switch (rawc->coltype)
- ]
417 : : {
418 : 101 : case JTC_FOR_ORDINALITY:
419 [ + + ]: 101 : if (ordinality_found)
420 [ + - ]: 4 : ereport(ERROR,
421 : : (errcode(ERRCODE_SYNTAX_ERROR),
422 : : errmsg("only one FOR ORDINALITY column is allowed"),
423 : : parser_errposition(pstate, rawc->location)));
424 : 97 : ordinality_found = true;
425 : 97 : colexpr = NULL;
426 : 97 : typid = INT4OID;
427 : 97 : typmod = -1;
428 : 97 : break;
429 : :
430 : 840 : case JTC_REGULAR:
431 : 840 : typenameTypeIdAndMod(pstate, rawc->typeName, &typid, &typmod);
432 : :
433 : : /*
434 : : * Use JTC_FORMATTED so as to use JSON_QUERY for this column
435 : : * if the specified type is one that's better handled using
436 : : * JSON_QUERY() or if non-default WRAPPER or QUOTES behavior
437 : : * is specified.
438 : : */
439 [ + + ]: 840 : if (isCompositeType(typid) ||
440 [ + + ]: 707 : rawc->quotes != JS_QUOTES_UNSPEC ||
441 [ - + ]: 671 : rawc->wrapper != JSW_UNSPEC)
442 : 169 : rawc->coltype = JTC_FORMATTED;
443 : :
444 : : pg_fallthrough;
445 : : case JTC_FORMATTED:
446 : : case JTC_EXISTS:
447 : : {
448 : : JsonFuncExpr *jfe;
449 : 1059 : CaseTestExpr *param = makeNode(CaseTestExpr);
450 : :
451 : 1059 : param->collation = InvalidOid;
452 : 1059 : param->typeId = contextItemTypid;
453 : 1059 : param->typeMod = -1;
454 : :
455 : 1059 : jfe = transformJsonTableColumn(rawc, (Node *) param,
456 : : passingArgs);
457 : :
458 : 1059 : colexpr = transformExpr(pstate, (Node *) jfe,
459 : : EXPR_KIND_FROM_FUNCTION);
460 : 1043 : assign_expr_collations(pstate, colexpr);
461 : :
462 : 1043 : typid = exprType(colexpr);
463 : 1043 : typmod = exprTypmod(colexpr);
464 : 1043 : typcoll = exprCollation(colexpr);
465 : 1043 : break;
466 : : }
467 : :
468 : 384 : case JTC_NESTED:
469 : 384 : continue;
470 : :
471 : 0 : default:
472 [ # # ]: 0 : elog(ERROR, "unknown JSON_TABLE column type: %d", (int) rawc->coltype);
473 : : break;
474 : : }
475 : :
476 : 1140 : tf->coltypes = lappend_oid(tf->coltypes, typid);
477 : 1140 : tf->coltypmods = lappend_int(tf->coltypmods, typmod);
478 : 1140 : tf->colcollations = lappend_oid(tf->colcollations, typcoll);
479 : 1140 : tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
480 : : }
481 : 862 : }
482 : :
483 : : /*
484 : : * Check if the type is "composite" for the purpose of checking whether to use
485 : : * JSON_VALUE() or JSON_QUERY() for a given JsonTableColumn.
486 : : */
487 : : static bool
488 : 868 : isCompositeType(Oid typid)
489 : : {
490 : 868 : char typtype = get_typtype(typid);
491 : :
492 [ + + ]: 832 : return typid == JSONOID ||
493 [ + - ]: 783 : typid == JSONBOID ||
494 [ + + ]: 783 : typid == RECORDOID ||
495 [ + + ]: 1522 : type_is_array(typid) ||
496 [ + + + + ]: 1728 : typtype == TYPTYPE_COMPOSITE ||
497 : : /* domain over one of the above? */
498 [ - + ]: 28 : (typtype == TYPTYPE_DOMAIN &&
499 : 28 : isCompositeType(getBaseType(typid)));
500 : : }
501 : :
502 : : /*
503 : : * Transform JSON_TABLE column definition into a JsonFuncExpr
504 : : * This turns:
505 : : * - regular column into JSON_VALUE()
506 : : * - FORMAT JSON column into JSON_QUERY()
507 : : * - EXISTS column into JSON_EXISTS()
508 : : */
509 : : static JsonFuncExpr *
510 : 1059 : transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
511 : : List *passingArgs)
512 : : {
513 : : Node *pathspec;
514 : 1059 : JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
515 : :
516 [ + + ]: 1059 : if (jtc->coltype == JTC_REGULAR)
517 : 671 : jfexpr->op = JSON_VALUE_OP;
518 [ + + ]: 388 : else if (jtc->coltype == JTC_EXISTS)
519 : 117 : jfexpr->op = JSON_EXISTS_OP;
520 : : else
521 : 271 : jfexpr->op = JSON_QUERY_OP;
522 : :
523 : : /* Pass the column name so any runtime JsonExpr errors can print it. */
524 : : Assert(jtc->name != NULL);
525 : 1059 : jfexpr->column_name = pstrdup(jtc->name);
526 : :
527 : 1059 : jfexpr->context_item = makeJsonValueExpr((Expr *) contextItemExpr, NULL,
528 : : makeJsonFormat(JS_FORMAT_DEFAULT,
529 : : JS_ENC_DEFAULT,
530 : : -1));
531 [ + + ]: 1059 : if (jtc->pathspec)
532 : 973 : pathspec = (Node *) jtc->pathspec->string;
533 : : else
534 : : {
535 : : /* Construct default path as '$."column_name"' */
536 : : StringInfoData path;
537 : :
538 : 86 : initStringInfo(&path);
539 : :
540 : 86 : appendStringInfoString(&path, "$.");
541 : 86 : escape_json(&path, jtc->name);
542 : :
543 : 86 : pathspec = makeStringConst(path.data, -1);
544 : : }
545 : 1059 : jfexpr->pathspec = pathspec;
546 : : /* Each column gets its own copy; see transformJsonTable(). */
547 : 1059 : jfexpr->passing = copyObject(passingArgs);
548 : 1059 : jfexpr->output = makeNode(JsonOutput);
549 : 1059 : jfexpr->output->typeName = jtc->typeName;
550 : 1059 : jfexpr->output->returning = makeNode(JsonReturning);
551 : 1059 : jfexpr->output->returning->format = jtc->format;
552 : 1059 : jfexpr->on_empty = jtc->on_empty;
553 : 1059 : jfexpr->on_error = jtc->on_error;
554 : 1059 : jfexpr->quotes = jtc->quotes;
555 : 1059 : jfexpr->wrapper = jtc->wrapper;
556 : 1059 : jfexpr->location = jtc->location;
557 : :
558 : 1059 : return jfexpr;
559 : : }
560 : :
561 : : static JsonTableColumn *
562 : 142 : findNestedJsonTableColumn(List *columns, const char *pathname)
563 : : {
564 : : ListCell *lc;
565 : :
566 [ + - + - : 525 : foreach(lc, columns)
+ - ]
567 : : {
568 : 525 : JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
569 : :
570 [ + + ]: 525 : if (jtc->coltype == JTC_NESTED &&
571 [ + - ]: 199 : jtc->pathspec->name &&
572 [ + + ]: 199 : !strcmp(jtc->pathspec->name, pathname))
573 : 142 : return jtc;
574 : : }
575 : :
576 : 0 : return NULL;
577 : : }
578 : :
579 : : /*
580 : : * Recursively transform nested columns and create child plan(s) that will be
581 : : * used to evaluate their row patterns.
582 : : *
583 : : * Default plan is transformed into a cross/union join of its nested columns.
584 : : * Simple and outer/inner plans are transformed into a JsonTablePlan by
585 : : * finding and transforming corresponding nested column.
586 : : * Sibling plans are recursively transformed into a JsonTableSiblingJoin.
587 : : */
588 : : static JsonTablePlan *
589 : 881 : transformJsonTableNestedColumns(JsonTableParseContext *cxt,
590 : : JsonTablePlanSpec *planspec,
591 : : List *columns,
592 : : List *passingArgs)
593 : : {
594 : 881 : JsonTableColumn *jtc = NULL;
595 : :
596 [ + + + + ]: 881 : if (!planspec || planspec->plan_type == JSTP_DEFAULT)
597 : : {
598 : : /* unspecified or default plan */
599 : 676 : JsonTablePlan *plan = NULL;
600 : : ListCell *lc;
601 [ + + + + ]: 676 : bool cross = planspec && (planspec->join_type & JSTP_JOIN_CROSS);
602 : :
603 : : /*
604 : : * If there are multiple NESTED COLUMNS clauses in 'columns', their
605 : : * respective plans will be combined using a "sibling join" plan,
606 : : * which effectively does a UNION of the sets of rows coming from each
607 : : * nested plan.
608 : : */
609 [ + - + + : 1776 : foreach(lc, columns)
+ + ]
610 : : {
611 : 1100 : JsonTableColumn *col = castNode(JsonTableColumn, lfirst(lc));
612 : : JsonTablePlan *nested;
613 : :
614 [ + + ]: 1100 : if (col->coltype != JTC_NESTED)
615 : 866 : continue;
616 : :
617 [ + + ]: 234 : if (col->pathspec->name == NULL)
618 : : {
619 : 106 : col->pathspec->name = generateJsonTablePathName(cxt);
620 : : }
621 : :
622 : 234 : nested = transformJsonTableColumns(cxt, planspec, col->columns,
623 : : passingArgs,
624 : : col->pathspec);
625 : :
626 : : /* Join nested plan with previous sibling nested plans. */
627 [ + + ]: 234 : if (plan)
628 : 84 : plan = makeJsonTableSiblingJoin(cross, plan, nested);
629 : : else
630 : 150 : plan = nested;
631 : : }
632 : :
633 : 676 : return plan;
634 : : }
635 [ + + ]: 205 : else if (planspec->plan_type == JSTP_SIMPLE)
636 : : {
637 : 106 : jtc = findNestedJsonTableColumn(columns, planspec->pathname);
638 : : }
639 [ + - ]: 99 : else if (planspec->plan_type == JSTP_JOINED)
640 : : {
641 [ + + ]: 99 : if (planspec->join_type == JSTP_JOIN_INNER ||
642 [ + + ]: 75 : planspec->join_type == JSTP_JOIN_OUTER)
643 : : {
644 : : Assert(planspec->plan1->plan_type == JSTP_SIMPLE);
645 : 36 : jtc = findNestedJsonTableColumn(columns, planspec->plan1->pathname);
646 : : }
647 : : else
648 : : {
649 : 63 : JsonTablePlan *lplan = transformJsonTableNestedColumns(cxt,
650 : 63 : planspec->plan1,
651 : : columns,
652 : : passingArgs);
653 : 55 : JsonTablePlan *rplan = transformJsonTableNestedColumns(cxt,
654 : 55 : planspec->plan2,
655 : : columns,
656 : : passingArgs);
657 : :
658 : 51 : return makeJsonTableSiblingJoin(planspec->join_type == JSTP_JOIN_CROSS,
659 : : lplan, rplan);
660 : : }
661 : : }
662 : : else
663 [ # # ]: 0 : elog(ERROR, "invalid JSON_TABLE plan type %d", planspec->plan_type);
664 : :
665 : : /*
666 : : * The plan's path names were already matched one-to-one against the
667 : : * nested columns by validateJsonTableChildPlan(), so a nested column with
668 : : * this path name must exist.
669 : : */
670 : : Assert(jtc != NULL);
671 : :
672 : 142 : return transformJsonTableColumns(cxt, planspec, jtc->columns,
673 : : passingArgs,
674 : : jtc->pathspec);
675 : : }
676 : :
677 : : /*
678 : : * Create transformed JSON_TABLE parent plan node by appending all non-nested
679 : : * columns to the TableFunc node and remembering their indices in the
680 : : * colvalexprs list.
681 : : *
682 : : * colMin and colMax give the range of columns computed by this scan in the
683 : : * global flat list of column expressions that will be passed to the
684 : : * JSON_TABLE's TableFunc. Both are -1 when all of columns are nested and
685 : : * thus computed by 'childplan'.
686 : : */
687 : : static JsonTablePlan *
688 : 850 : makeJsonTablePathScan(JsonTableParseContext *cxt, JsonTablePathSpec *pathspec,
689 : : JsonTablePlanSpec *planspec,
690 : : bool errorOnError,
691 : : int colMin, int colMax,
692 : : JsonTablePlan *childplan)
693 : : {
694 : 850 : JsonTablePathScan *scan = makeNode(JsonTablePathScan);
695 : : char *pathstring;
696 : : Const *value;
697 : :
698 : : Assert(IsA(pathspec->string, A_Const));
699 : 850 : pathstring = castNode(A_Const, pathspec->string)->val.sval.sval;
700 : 850 : value = makeConst(JSONPATHOID, -1, InvalidOid, -1,
701 : : DirectFunctionCall1(jsonpath_in,
702 : : CStringGetDatum(pathstring)),
703 : : false, false);
704 : :
705 : 850 : scan->plan.type = T_JsonTablePathScan;
706 : 850 : scan->path = makeJsonTablePath(value, pathspec->name);
707 : 850 : scan->errorOnError = errorOnError;
708 : :
709 : 850 : scan->child = childplan;
710 : :
711 : 850 : scan->colMin = colMin;
712 : 850 : scan->colMax = colMax;
713 : :
714 [ + + ]: 850 : if (scan->child)
715 [ + + ]: 324 : scan->outerJoin = planspec == NULL ||
716 [ + + ]: 324 : (planspec->join_type & JSTP_JOIN_OUTER);
717 : : /* else: default plan case, no children found */
718 : :
719 : 850 : return (JsonTablePlan *) scan;
720 : : }
721 : :
722 : : /*
723 : : * Create a JsonTablePlan that will perform a join of the rows coming from
724 : : * 'lplan' and 'rplan'.
725 : : *
726 : : * The default way of "joining" the rows is to perform a UNION between the
727 : : * sets of rows from 'lplan' and 'rplan'.
728 : : */
729 : : static JsonTablePlan *
730 : 135 : makeJsonTableSiblingJoin(bool cross, JsonTablePlan *lplan, JsonTablePlan *rplan)
731 : : {
732 : 135 : JsonTableSiblingJoin *join = makeNode(JsonTableSiblingJoin);
733 : :
734 : 135 : join->plan.type = T_JsonTableSiblingJoin;
735 : 135 : join->lplan = lplan;
736 : 135 : join->rplan = rplan;
737 : 135 : join->cross = cross;
738 : :
739 : 135 : return (JsonTablePlan *) join;
740 : : }
741 : :
742 : : /* Collect sibling path names from plan to the specified list. */
743 : : static void
744 : 257 : collectSiblingPathsInJsonTablePlan(JsonTablePlanSpec *plan, List **paths)
745 : : {
746 [ + + ]: 257 : if (plan->plan_type == JSTP_SIMPLE)
747 : 146 : *paths = lappend(*paths, plan->pathname);
748 [ + - ]: 111 : else if (plan->plan_type == JSTP_JOINED)
749 : : {
750 [ + + ]: 111 : if (plan->join_type == JSTP_JOIN_INNER ||
751 [ + + ]: 87 : plan->join_type == JSTP_JOIN_OUTER)
752 : : {
753 : : Assert(plan->plan1->plan_type == JSTP_SIMPLE);
754 : 36 : *paths = lappend(*paths, plan->plan1->pathname);
755 : : }
756 [ + + ]: 75 : else if (plan->join_type == JSTP_JOIN_CROSS ||
757 [ + - ]: 28 : plan->join_type == JSTP_JOIN_UNION)
758 : : {
759 : 75 : collectSiblingPathsInJsonTablePlan(plan->plan1, paths);
760 : 75 : collectSiblingPathsInJsonTablePlan(plan->plan2, paths);
761 : : }
762 : : else
763 [ # # ]: 0 : elog(ERROR, "invalid JSON_TABLE join type %d",
764 : : plan->join_type);
765 : : }
766 : 257 : }
767 : :
768 : : /*
769 : : * Validate child JSON_TABLE plan by checking that:
770 : : * - all nested columns have path names specified
771 : : * - all nested columns have corresponding node in the sibling plan
772 : : * - plan does not contain duplicate or extra nodes
773 : : */
774 : : static void
775 : 222 : validateJsonTableChildPlan(JsonTableParseContext *cxt, JsonTablePlanSpec *plan,
776 : : List *columns)
777 : : {
778 : 222 : ParseState *pstate = cxt->pstate;
779 : : ListCell *lc1;
780 : 222 : List *siblings = NIL;
781 : 222 : int nchildren = 0;
782 : :
783 [ + + ]: 222 : if (plan)
784 : 107 : collectSiblingPathsInJsonTablePlan(plan, &siblings);
785 : :
786 [ + - + + : 662 : foreach(lc1, columns)
+ + ]
787 : : {
788 : 472 : JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc1));
789 : :
790 [ + + ]: 472 : if (jtc->coltype == JTC_NESTED)
791 : : {
792 : : ListCell *lc2;
793 : 202 : bool found = false;
794 : :
795 : : /*
796 : : * A NESTED path need not be named; generate a name if it was left
797 : : * unnamed. A generated name cannot be referenced by a specific
798 : : * PLAN(), so such a path is reported just below as a nested path
799 : : * not covered by the plan.
800 : : */
801 [ + + ]: 202 : if (jtc->pathspec->name == NULL)
802 : 8 : jtc->pathspec->name = generateJsonTablePathName(cxt);
803 : :
804 : : /* find nested path name in the list of sibling path names */
805 [ + + + + : 295 : foreach(lc2, siblings)
+ + ]
806 : : {
807 [ + + ]: 263 : if ((found = !strcmp(jtc->pathspec->name, lfirst(lc2))))
808 : 170 : break;
809 : : }
810 : :
811 [ + + ]: 202 : if (!found)
812 [ + - ]: 32 : ereport(ERROR,
813 : : errcode(ERRCODE_SYNTAX_ERROR),
814 : : errmsg("invalid JSON_TABLE specification"),
815 : : errdetail("PLAN clause for nested path %s was not found.",
816 : : jtc->pathspec->name),
817 : : parser_errposition(pstate, jtc->location));
818 : :
819 : 170 : nchildren++;
820 : : }
821 : : }
822 : :
823 [ + + ]: 190 : if (list_length(siblings) > nchildren)
824 [ + - + - ]: 4 : ereport(ERROR,
825 : : errcode(ERRCODE_SYNTAX_ERROR),
826 : : errmsg("invalid JSON_TABLE plan clause"),
827 : : errdetail("PLAN clause contains some extra or duplicate sibling nodes."),
828 : : parser_errposition(pstate, plan ? plan->location : -1));
829 : 186 : }
|