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 : 566 : transformJsonTable(ParseState *pstate, JsonTable *jt)
85 : : {
86 : : TableFunc *tf;
87 : : JsonFuncExpr *jfe;
88 : : JsonExpr *je;
89 : 566 : JsonTablePlanSpec *plan = jt->planspec;
90 : 566 : JsonTablePathSpec *rootPathSpec = jt->pathspec;
91 : : bool is_lateral;
92 : 566 : JsonTableParseContext cxt = {pstate};
93 : :
94 : : Assert(IsA(rootPathSpec->string, A_Const) &&
95 : : castNode(A_Const, rootPathSpec->string)->val.node.type == T_String);
96 : :
97 [ + + ]: 566 : 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 : 554 : 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 [ + + ]: 554 : if (rootPathSpec->name != NULL)
115 : 160 : cxt.pathNames = list_make1(rootPathSpec->name);
116 : 554 : 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 [ + + ]: 534 : if (rootPathSpec->name == NULL)
128 : 382 : 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 : 534 : pstate->p_lateral_active = true;
141 : :
142 : 534 : tf = makeNode(TableFunc);
143 : 534 : 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 : 534 : jfe = makeNode(JsonFuncExpr);
156 : 534 : jfe->op = JSON_TABLE_OP;
157 : 534 : jfe->context_item = jt->context_item;
158 : 534 : jfe->pathspec = (Node *) rootPathSpec->string;
159 : 534 : jfe->passing = copyObject(jt->passing);
160 : 534 : jfe->on_empty = NULL;
161 : 534 : jfe->on_error = jt->on_error;
162 : 534 : jfe->location = jt->location;
163 : 534 : tf->docexpr = transformExpr(pstate, (Node *) jfe, EXPR_KIND_FROM_FUNCTION);
164 : :
165 : : /*
166 : : * Create a JsonTablePlan that will generate row pattern that becomes
167 : : * source data for JSON path expressions in jt->columns. This also adds
168 : : * the columns' transformed JsonExpr nodes into tf->colvalexprs.
169 : : */
170 : 534 : cxt.jt = jt;
171 : 534 : cxt.tf = tf;
172 : 534 : tf->plan = (Node *) transformJsonTableColumns(&cxt, plan, jt->columns,
173 : : jt->passing,
174 : : rootPathSpec);
175 : :
176 : : /*
177 : : * Copy the transformed PASSING arguments into the TableFunc node, because
178 : : * they are evaluated separately from the JsonExpr that we just put in
179 : : * TableFunc.docexpr. JsonExpr.passing_values is still kept around for
180 : : * get_json_table().
181 : : */
182 : 466 : je = (JsonExpr *) tf->docexpr;
183 : 466 : tf->passingvalexprs = copyObject(je->passing_values);
184 : :
185 : 466 : tf->ordinalitycol = -1; /* undefine ordinality column number */
186 : 466 : tf->location = jt->location;
187 : :
188 : 466 : pstate->p_lateral_active = false;
189 : :
190 : : /*
191 : : * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
192 : : * there are any lateral cross-references in it.
193 : : */
194 [ + - + + ]: 466 : is_lateral = jt->lateral || contain_vars_of_level((Node *) tf, 0);
195 : :
196 : 466 : return addRangeTableEntryForTableFunc(pstate,
197 : : tf, jt->alias, is_lateral, true);
198 : : }
199 : :
200 : : /*
201 : : * Check if a column / path name is duplicated in the given shared list of
202 : : * names.
203 : : */
204 : : static void
205 : 1126 : CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
206 : : List *columns)
207 : : {
208 : : ListCell *lc1;
209 : :
210 [ + - + + : 2978 : foreach(lc1, columns)
+ + ]
211 : : {
212 : 1876 : JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc1));
213 : :
214 [ + + ]: 1876 : if (jtc->coltype == JTC_NESTED)
215 : : {
216 [ + + ]: 584 : if (jtc->pathspec->name)
217 : : {
218 [ + + ]: 466 : if (LookupPathOrColumnName(cxt, jtc->pathspec->name))
219 [ + - ]: 12 : ereport(ERROR,
220 : : errcode(ERRCODE_DUPLICATE_ALIAS),
221 : : errmsg("duplicate JSON_TABLE column or path name: %s",
222 : : jtc->pathspec->name),
223 : : parser_errposition(cxt->pstate,
224 : : jtc->pathspec->name_location));
225 : 454 : cxt->pathNames = lappend(cxt->pathNames, jtc->pathspec->name);
226 : : }
227 : :
228 : 572 : CheckDuplicateColumnOrPathNames(cxt, jtc->columns);
229 : : }
230 : : else
231 : : {
232 [ + + ]: 1292 : if (LookupPathOrColumnName(cxt, jtc->name))
233 [ + - ]: 8 : ereport(ERROR,
234 : : errcode(ERRCODE_DUPLICATE_ALIAS),
235 : : errmsg("duplicate JSON_TABLE column or path name: %s",
236 : : jtc->name),
237 : : parser_errposition(cxt->pstate, jtc->location));
238 : 1284 : cxt->pathNames = lappend(cxt->pathNames, jtc->name);
239 : : }
240 : : }
241 : 1102 : }
242 : :
243 : : /*
244 : : * Lookup a column/path name in the given name list, returning true if already
245 : : * there.
246 : : */
247 : : static bool
248 : 2262 : LookupPathOrColumnName(JsonTableParseContext *cxt, char *name)
249 : : {
250 : : ListCell *lc;
251 : :
252 [ + + + + : 10846 : foreach(lc, cxt->pathNames)
+ + ]
253 : : {
254 [ + + ]: 8612 : if (strcmp(name, (const char *) lfirst(lc)) == 0)
255 : 28 : return true;
256 : : }
257 : :
258 : 2234 : return false;
259 : : }
260 : :
261 : : /* Generate a new unique JSON_TABLE path name. */
262 : : static char *
263 : 496 : generateJsonTablePathName(JsonTableParseContext *cxt)
264 : : {
265 : : char namebuf[32];
266 : : char *name;
267 : :
268 : : /*
269 : : * Bump the counter until we produce a name that is not already used as a
270 : : * path or column name. Otherwise a generated name could coincide with a
271 : : * user-supplied one, which would confuse PLAN clause matching and could
272 : : * silently drop a column.
273 : : */
274 : : do
275 : : {
276 : 504 : snprintf(namebuf, sizeof(namebuf), "json_table_path_%d",
277 : 504 : cxt->pathNameId++);
278 [ + + ]: 504 : } while (LookupPathOrColumnName(cxt, namebuf));
279 : :
280 : 496 : name = pstrdup(namebuf);
281 : 496 : cxt->pathNames = lappend(cxt->pathNames, name);
282 : :
283 : 496 : return name;
284 : : }
285 : :
286 : : /*
287 : : * Create a JsonTablePlan that will supply the source row for 'columns'
288 : : * using 'pathspec' and append the columns' transformed JsonExpr nodes and
289 : : * their type/collation information to cxt->tf.
290 : : */
291 : : static JsonTablePlan *
292 : 910 : transformJsonTableColumns(JsonTableParseContext *cxt,
293 : : JsonTablePlanSpec *planspec,
294 : : List *columns,
295 : : List *passingArgs,
296 : : JsonTablePathSpec *pathspec)
297 : : {
298 : 910 : JsonTable *jt = cxt->jt;
299 : 910 : TableFunc *tf = cxt->tf;
300 : : JsonTablePathScan *scan;
301 : : JsonTablePlanSpec *childPlanSpec;
302 [ + + ]: 1216 : bool defaultPlan = planspec == NULL ||
303 [ + + ]: 306 : planspec->plan_type == JSTP_DEFAULT;
304 [ + + ]: 949 : bool errorOnError = jt->on_error &&
305 [ + + ]: 39 : jt->on_error->btype == JSON_BEHAVIOR_ERROR;
306 : : int colMin,
307 : : colMax;
308 : 910 : JsonTablePlan *childplan = NULL;
309 : :
310 : : /* Start of column range */
311 : 910 : colMin = list_length(tf->colvalexprs);
312 : :
313 [ + + ]: 910 : if (defaultPlan)
314 : 676 : childPlanSpec = planspec;
315 : : else
316 : : {
317 : : /* validate parent and child plans */
318 : : JsonTablePlanSpec *parentPlanSpec;
319 : :
320 [ + + ]: 234 : if (planspec->plan_type == JSTP_JOINED)
321 : : {
322 [ + + ]: 115 : if (planspec->join_type != JSTP_JOIN_INNER &&
323 [ + + ]: 81 : planspec->join_type != JSTP_JOIN_OUTER)
324 [ + - ]: 8 : ereport(ERROR,
325 : : (errcode(ERRCODE_SYNTAX_ERROR),
326 : : errmsg("invalid JSON_TABLE plan clause"),
327 : : errdetail("Expected INNER or OUTER."),
328 : : parser_errposition(cxt->pstate, planspec->location)));
329 : :
330 : 107 : parentPlanSpec = planspec->plan1;
331 : 107 : childPlanSpec = planspec->plan2;
332 : :
333 : : Assert(parentPlanSpec->plan_type != JSTP_JOINED);
334 : : Assert(parentPlanSpec->pathname);
335 : : }
336 : : else
337 : : {
338 : 119 : parentPlanSpec = planspec;
339 : 119 : childPlanSpec = NULL;
340 : : }
341 : :
342 [ + + ]: 226 : if (strcmp(parentPlanSpec->pathname, pathspec->name) != 0)
343 [ + - ]: 4 : ereport(ERROR,
344 : : (errcode(ERRCODE_SYNTAX_ERROR),
345 : : errmsg("invalid JSON_TABLE plan"),
346 : : errdetail("PATH name mismatch: expected %s but %s is given.",
347 : : pathspec->name, parentPlanSpec->pathname),
348 : : parser_errposition(cxt->pstate, planspec->location)));
349 : :
350 : 222 : validateJsonTableChildPlan(cxt, childPlanSpec, columns);
351 : : }
352 : :
353 : 862 : appendJsonTableColumns(cxt, columns, passingArgs);
354 : :
355 : : /* End of column range. */
356 [ + + ]: 842 : if (list_length(tf->colvalexprs) == colMin)
357 : : {
358 : : /* No columns in this Scan beside the nested ones. */
359 : 116 : colMax = colMin = -1;
360 : : }
361 : : else
362 : 726 : colMax = list_length(tf->colvalexprs) - 1;
363 : :
364 [ + + + + ]: 842 : if (childPlanSpec || defaultPlan)
365 : : {
366 : : /* transform recursively nested columns */
367 : 743 : childplan = transformJsonTableNestedColumns(cxt, childPlanSpec,
368 : : columns, passingArgs);
369 : : }
370 : :
371 : : /* transform only non-nested columns */
372 : 830 : scan = (JsonTablePathScan *) makeJsonTablePathScan(cxt, pathspec,
373 : : planspec,
374 : : errorOnError,
375 : : colMin,
376 : : colMax,
377 : : childplan);
378 : :
379 : 830 : return (JsonTablePlan *) scan;
380 : : }
381 : :
382 : : /* Append transformed non-nested JSON_TABLE columns to the TableFunc node */
383 : : static void
384 : 862 : appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingArgs)
385 : : {
386 : : ListCell *col;
387 : 862 : ParseState *pstate = cxt->pstate;
388 : 862 : TableFunc *tf = cxt->tf;
389 : 862 : bool ordinality_found = false;
390 : 862 : Oid contextItemTypid = exprType(tf->docexpr);
391 : :
392 [ + - + + : 2366 : foreach(col, columns)
+ + ]
393 : : {
394 : 1524 : JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col));
395 : : Oid typid;
396 : : int32 typmod;
397 : 1524 : Oid typcoll = InvalidOid;
398 : : Node *colexpr;
399 : :
400 [ + + ]: 1524 : if (rawc->name)
401 : 1140 : tf->colnames = lappend(tf->colnames,
402 : 1140 : makeString(pstrdup(rawc->name)));
403 : :
404 : : /*
405 : : * Determine the type and typmod for the new column. FOR ORDINALITY
406 : : * columns are INTEGER by standard; the others are user-specified.
407 : : */
408 [ + + + + : 1524 : switch (rawc->coltype)
- ]
409 : : {
410 : 101 : case JTC_FOR_ORDINALITY:
411 [ + + ]: 101 : if (ordinality_found)
412 [ + - ]: 4 : ereport(ERROR,
413 : : (errcode(ERRCODE_SYNTAX_ERROR),
414 : : errmsg("only one FOR ORDINALITY column is allowed"),
415 : : parser_errposition(pstate, rawc->location)));
416 : 97 : ordinality_found = true;
417 : 97 : colexpr = NULL;
418 : 97 : typid = INT4OID;
419 : 97 : typmod = -1;
420 : 97 : break;
421 : :
422 : 820 : case JTC_REGULAR:
423 : 820 : typenameTypeIdAndMod(pstate, rawc->typeName, &typid, &typmod);
424 : :
425 : : /*
426 : : * Use JTC_FORMATTED so as to use JSON_QUERY for this column
427 : : * if the specified type is one that's better handled using
428 : : * JSON_QUERY() or if non-default WRAPPER or QUOTES behavior
429 : : * is specified.
430 : : */
431 [ + + ]: 820 : if (isCompositeType(typid) ||
432 [ + + ]: 687 : rawc->quotes != JS_QUOTES_UNSPEC ||
433 [ - + ]: 651 : rawc->wrapper != JSW_UNSPEC)
434 : 169 : rawc->coltype = JTC_FORMATTED;
435 : :
436 : : pg_fallthrough;
437 : : case JTC_FORMATTED:
438 : : case JTC_EXISTS:
439 : : {
440 : : JsonFuncExpr *jfe;
441 : 1039 : CaseTestExpr *param = makeNode(CaseTestExpr);
442 : :
443 : 1039 : param->collation = InvalidOid;
444 : 1039 : param->typeId = contextItemTypid;
445 : 1039 : param->typeMod = -1;
446 : :
447 : 1039 : jfe = transformJsonTableColumn(rawc, (Node *) param,
448 : : passingArgs);
449 : :
450 : 1039 : colexpr = transformExpr(pstate, (Node *) jfe,
451 : : EXPR_KIND_FROM_FUNCTION);
452 : 1023 : assign_expr_collations(pstate, colexpr);
453 : :
454 : 1023 : typid = exprType(colexpr);
455 : 1023 : typmod = exprTypmod(colexpr);
456 : 1023 : typcoll = exprCollation(colexpr);
457 : 1023 : break;
458 : : }
459 : :
460 : 384 : case JTC_NESTED:
461 : 384 : continue;
462 : :
463 : 0 : default:
464 [ # # ]: 0 : elog(ERROR, "unknown JSON_TABLE column type: %d", (int) rawc->coltype);
465 : : break;
466 : : }
467 : :
468 : 1120 : tf->coltypes = lappend_oid(tf->coltypes, typid);
469 : 1120 : tf->coltypmods = lappend_int(tf->coltypmods, typmod);
470 : 1120 : tf->colcollations = lappend_oid(tf->colcollations, typcoll);
471 : 1120 : tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
472 : : }
473 : 842 : }
474 : :
475 : : /*
476 : : * Check if the type is "composite" for the purpose of checking whether to use
477 : : * JSON_VALUE() or JSON_QUERY() for a given JsonTableColumn.
478 : : */
479 : : static bool
480 : 848 : isCompositeType(Oid typid)
481 : : {
482 : 848 : char typtype = get_typtype(typid);
483 : :
484 [ + + ]: 812 : return typid == JSONOID ||
485 [ + - ]: 763 : typid == JSONBOID ||
486 [ + + ]: 763 : typid == RECORDOID ||
487 [ + + ]: 1482 : type_is_array(typid) ||
488 [ + + + + ]: 1688 : typtype == TYPTYPE_COMPOSITE ||
489 : : /* domain over one of the above? */
490 [ - + ]: 28 : (typtype == TYPTYPE_DOMAIN &&
491 : 28 : isCompositeType(getBaseType(typid)));
492 : : }
493 : :
494 : : /*
495 : : * Transform JSON_TABLE column definition into a JsonFuncExpr
496 : : * This turns:
497 : : * - regular column into JSON_VALUE()
498 : : * - FORMAT JSON column into JSON_QUERY()
499 : : * - EXISTS column into JSON_EXISTS()
500 : : */
501 : : static JsonFuncExpr *
502 : 1039 : transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
503 : : List *passingArgs)
504 : : {
505 : : Node *pathspec;
506 : 1039 : JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
507 : :
508 [ + + ]: 1039 : if (jtc->coltype == JTC_REGULAR)
509 : 651 : jfexpr->op = JSON_VALUE_OP;
510 [ + + ]: 388 : else if (jtc->coltype == JTC_EXISTS)
511 : 117 : jfexpr->op = JSON_EXISTS_OP;
512 : : else
513 : 271 : jfexpr->op = JSON_QUERY_OP;
514 : :
515 : : /* Pass the column name so any runtime JsonExpr errors can print it. */
516 : : Assert(jtc->name != NULL);
517 : 1039 : jfexpr->column_name = pstrdup(jtc->name);
518 : :
519 : 1039 : jfexpr->context_item = makeJsonValueExpr((Expr *) contextItemExpr, NULL,
520 : : makeJsonFormat(JS_FORMAT_DEFAULT,
521 : : JS_ENC_DEFAULT,
522 : : -1));
523 [ + + ]: 1039 : if (jtc->pathspec)
524 : 953 : pathspec = (Node *) jtc->pathspec->string;
525 : : else
526 : : {
527 : : /* Construct default path as '$."column_name"' */
528 : : StringInfoData path;
529 : :
530 : 86 : initStringInfo(&path);
531 : :
532 : 86 : appendStringInfoString(&path, "$.");
533 : 86 : escape_json(&path, jtc->name);
534 : :
535 : 86 : pathspec = makeStringConst(path.data, -1);
536 : : }
537 : 1039 : jfexpr->pathspec = pathspec;
538 : : /* Each column gets its own copy; see transformJsonTable(). */
539 : 1039 : jfexpr->passing = copyObject(passingArgs);
540 : 1039 : jfexpr->output = makeNode(JsonOutput);
541 : 1039 : jfexpr->output->typeName = jtc->typeName;
542 : 1039 : jfexpr->output->returning = makeNode(JsonReturning);
543 : 1039 : jfexpr->output->returning->format = jtc->format;
544 : 1039 : jfexpr->on_empty = jtc->on_empty;
545 : 1039 : jfexpr->on_error = jtc->on_error;
546 : 1039 : jfexpr->quotes = jtc->quotes;
547 : 1039 : jfexpr->wrapper = jtc->wrapper;
548 : 1039 : jfexpr->location = jtc->location;
549 : :
550 : 1039 : return jfexpr;
551 : : }
552 : :
553 : : static JsonTableColumn *
554 : 142 : findNestedJsonTableColumn(List *columns, const char *pathname)
555 : : {
556 : : ListCell *lc;
557 : :
558 [ + - + - : 525 : foreach(lc, columns)
+ - ]
559 : : {
560 : 525 : JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
561 : :
562 [ + + ]: 525 : if (jtc->coltype == JTC_NESTED &&
563 [ + - ]: 199 : jtc->pathspec->name &&
564 [ + + ]: 199 : !strcmp(jtc->pathspec->name, pathname))
565 : 142 : return jtc;
566 : : }
567 : :
568 : 0 : return NULL;
569 : : }
570 : :
571 : : /*
572 : : * Recursively transform nested columns and create child plan(s) that will be
573 : : * used to evaluate their row patterns.
574 : : *
575 : : * Default plan is transformed into a cross/union join of its nested columns.
576 : : * Simple and outer/inner plans are transformed into a JsonTablePlan by
577 : : * finding and transforming corresponding nested column.
578 : : * Sibling plans are recursively transformed into a JsonTableSiblingJoin.
579 : : */
580 : : static JsonTablePlan *
581 : 861 : transformJsonTableNestedColumns(JsonTableParseContext *cxt,
582 : : JsonTablePlanSpec *planspec,
583 : : List *columns,
584 : : List *passingArgs)
585 : : {
586 : 861 : JsonTableColumn *jtc = NULL;
587 : :
588 [ + + + + ]: 861 : if (!planspec || planspec->plan_type == JSTP_DEFAULT)
589 : : {
590 : : /* unspecified or default plan */
591 : 656 : JsonTablePlan *plan = NULL;
592 : : ListCell *lc;
593 [ + + + + ]: 656 : bool cross = planspec && (planspec->join_type & JSTP_JOIN_CROSS);
594 : :
595 : : /*
596 : : * If there are multiple NESTED COLUMNS clauses in 'columns', their
597 : : * respective plans will be combined using a "sibling join" plan,
598 : : * which effectively does a UNION of the sets of rows coming from each
599 : : * nested plan.
600 : : */
601 [ + - + + : 1736 : foreach(lc, columns)
+ + ]
602 : : {
603 : 1080 : JsonTableColumn *col = castNode(JsonTableColumn, lfirst(lc));
604 : : JsonTablePlan *nested;
605 : :
606 [ + + ]: 1080 : if (col->coltype != JTC_NESTED)
607 : 846 : continue;
608 : :
609 [ + + ]: 234 : if (col->pathspec->name == NULL)
610 : : {
611 : 106 : col->pathspec->name = generateJsonTablePathName(cxt);
612 : : }
613 : :
614 : 234 : nested = transformJsonTableColumns(cxt, planspec, col->columns,
615 : : passingArgs,
616 : : col->pathspec);
617 : :
618 : : /* Join nested plan with previous sibling nested plans. */
619 [ + + ]: 234 : if (plan)
620 : 84 : plan = makeJsonTableSiblingJoin(cross, plan, nested);
621 : : else
622 : 150 : plan = nested;
623 : : }
624 : :
625 : 656 : return plan;
626 : : }
627 [ + + ]: 205 : else if (planspec->plan_type == JSTP_SIMPLE)
628 : : {
629 : 106 : jtc = findNestedJsonTableColumn(columns, planspec->pathname);
630 : : }
631 [ + - ]: 99 : else if (planspec->plan_type == JSTP_JOINED)
632 : : {
633 [ + + ]: 99 : if (planspec->join_type == JSTP_JOIN_INNER ||
634 [ + + ]: 75 : planspec->join_type == JSTP_JOIN_OUTER)
635 : : {
636 : : Assert(planspec->plan1->plan_type == JSTP_SIMPLE);
637 : 36 : jtc = findNestedJsonTableColumn(columns, planspec->plan1->pathname);
638 : : }
639 : : else
640 : : {
641 : 63 : JsonTablePlan *lplan = transformJsonTableNestedColumns(cxt,
642 : 63 : planspec->plan1,
643 : : columns,
644 : : passingArgs);
645 : 55 : JsonTablePlan *rplan = transformJsonTableNestedColumns(cxt,
646 : 55 : planspec->plan2,
647 : : columns,
648 : : passingArgs);
649 : :
650 : 51 : return makeJsonTableSiblingJoin(planspec->join_type == JSTP_JOIN_CROSS,
651 : : lplan, rplan);
652 : : }
653 : : }
654 : : else
655 [ # # ]: 0 : elog(ERROR, "invalid JSON_TABLE plan type %d", planspec->plan_type);
656 : :
657 : : /*
658 : : * The plan's path names were already matched one-to-one against the
659 : : * nested columns by validateJsonTableChildPlan(), so a nested column with
660 : : * this path name must exist.
661 : : */
662 : : Assert(jtc != NULL);
663 : :
664 : 142 : return transformJsonTableColumns(cxt, planspec, jtc->columns,
665 : : passingArgs,
666 : : jtc->pathspec);
667 : : }
668 : :
669 : : /*
670 : : * Create transformed JSON_TABLE parent plan node by appending all non-nested
671 : : * columns to the TableFunc node and remembering their indices in the
672 : : * colvalexprs list.
673 : : *
674 : : * colMin and colMax give the range of columns computed by this scan in the
675 : : * global flat list of column expressions that will be passed to the
676 : : * JSON_TABLE's TableFunc. Both are -1 when all of columns are nested and
677 : : * thus computed by 'childplan'.
678 : : */
679 : : static JsonTablePlan *
680 : 830 : makeJsonTablePathScan(JsonTableParseContext *cxt, JsonTablePathSpec *pathspec,
681 : : JsonTablePlanSpec *planspec,
682 : : bool errorOnError,
683 : : int colMin, int colMax,
684 : : JsonTablePlan *childplan)
685 : : {
686 : 830 : JsonTablePathScan *scan = makeNode(JsonTablePathScan);
687 : : char *pathstring;
688 : : Const *value;
689 : :
690 : : Assert(IsA(pathspec->string, A_Const));
691 : 830 : pathstring = castNode(A_Const, pathspec->string)->val.sval.sval;
692 : 830 : value = makeConst(JSONPATHOID, -1, InvalidOid, -1,
693 : : DirectFunctionCall1(jsonpath_in,
694 : : CStringGetDatum(pathstring)),
695 : : false, false);
696 : :
697 : 830 : scan->plan.type = T_JsonTablePathScan;
698 : 830 : scan->path = makeJsonTablePath(value, pathspec->name);
699 : 830 : scan->errorOnError = errorOnError;
700 : :
701 : 830 : scan->child = childplan;
702 : :
703 : 830 : scan->colMin = colMin;
704 : 830 : scan->colMax = colMax;
705 : :
706 [ + + ]: 830 : if (scan->child)
707 [ + + ]: 324 : scan->outerJoin = planspec == NULL ||
708 [ + + ]: 324 : (planspec->join_type & JSTP_JOIN_OUTER);
709 : : /* else: default plan case, no children found */
710 : :
711 : 830 : return (JsonTablePlan *) scan;
712 : : }
713 : :
714 : : /*
715 : : * Create a JsonTablePlan that will perform a join of the rows coming from
716 : : * 'lplan' and 'rplan'.
717 : : *
718 : : * The default way of "joining" the rows is to perform a UNION between the
719 : : * sets of rows from 'lplan' and 'rplan'.
720 : : */
721 : : static JsonTablePlan *
722 : 135 : makeJsonTableSiblingJoin(bool cross, JsonTablePlan *lplan, JsonTablePlan *rplan)
723 : : {
724 : 135 : JsonTableSiblingJoin *join = makeNode(JsonTableSiblingJoin);
725 : :
726 : 135 : join->plan.type = T_JsonTableSiblingJoin;
727 : 135 : join->lplan = lplan;
728 : 135 : join->rplan = rplan;
729 : 135 : join->cross = cross;
730 : :
731 : 135 : return (JsonTablePlan *) join;
732 : : }
733 : :
734 : : /* Collect sibling path names from plan to the specified list. */
735 : : static void
736 : 257 : collectSiblingPathsInJsonTablePlan(JsonTablePlanSpec *plan, List **paths)
737 : : {
738 [ + + ]: 257 : if (plan->plan_type == JSTP_SIMPLE)
739 : 146 : *paths = lappend(*paths, plan->pathname);
740 [ + - ]: 111 : else if (plan->plan_type == JSTP_JOINED)
741 : : {
742 [ + + ]: 111 : if (plan->join_type == JSTP_JOIN_INNER ||
743 [ + + ]: 87 : plan->join_type == JSTP_JOIN_OUTER)
744 : : {
745 : : Assert(plan->plan1->plan_type == JSTP_SIMPLE);
746 : 36 : *paths = lappend(*paths, plan->plan1->pathname);
747 : : }
748 [ + + ]: 75 : else if (plan->join_type == JSTP_JOIN_CROSS ||
749 [ + - ]: 28 : plan->join_type == JSTP_JOIN_UNION)
750 : : {
751 : 75 : collectSiblingPathsInJsonTablePlan(plan->plan1, paths);
752 : 75 : collectSiblingPathsInJsonTablePlan(plan->plan2, paths);
753 : : }
754 : : else
755 [ # # ]: 0 : elog(ERROR, "invalid JSON_TABLE join type %d",
756 : : plan->join_type);
757 : : }
758 : 257 : }
759 : :
760 : : /*
761 : : * Validate child JSON_TABLE plan by checking that:
762 : : * - all nested columns have path names specified
763 : : * - all nested columns have corresponding node in the sibling plan
764 : : * - plan does not contain duplicate or extra nodes
765 : : */
766 : : static void
767 : 222 : validateJsonTableChildPlan(JsonTableParseContext *cxt, JsonTablePlanSpec *plan,
768 : : List *columns)
769 : : {
770 : 222 : ParseState *pstate = cxt->pstate;
771 : : ListCell *lc1;
772 : 222 : List *siblings = NIL;
773 : 222 : int nchildren = 0;
774 : :
775 [ + + ]: 222 : if (plan)
776 : 107 : collectSiblingPathsInJsonTablePlan(plan, &siblings);
777 : :
778 [ + - + + : 662 : foreach(lc1, columns)
+ + ]
779 : : {
780 : 472 : JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc1));
781 : :
782 [ + + ]: 472 : if (jtc->coltype == JTC_NESTED)
783 : : {
784 : : ListCell *lc2;
785 : 202 : bool found = false;
786 : :
787 : : /*
788 : : * A NESTED path need not be named; generate a name if it was left
789 : : * unnamed. A generated name cannot be referenced by a specific
790 : : * PLAN(), so such a path is reported just below as a nested path
791 : : * not covered by the plan.
792 : : */
793 [ + + ]: 202 : if (jtc->pathspec->name == NULL)
794 : 8 : jtc->pathspec->name = generateJsonTablePathName(cxt);
795 : :
796 : : /* find nested path name in the list of sibling path names */
797 [ + + + + : 295 : foreach(lc2, siblings)
+ + ]
798 : : {
799 [ + + ]: 263 : if ((found = !strcmp(jtc->pathspec->name, lfirst(lc2))))
800 : 170 : break;
801 : : }
802 : :
803 [ + + ]: 202 : if (!found)
804 [ + - ]: 32 : ereport(ERROR,
805 : : errcode(ERRCODE_SYNTAX_ERROR),
806 : : errmsg("invalid JSON_TABLE specification"),
807 : : errdetail("PLAN clause for nested path %s was not found.",
808 : : jtc->pathspec->name),
809 : : parser_errposition(pstate, jtc->location));
810 : :
811 : 170 : nchildren++;
812 : : }
813 : : }
814 : :
815 [ + + ]: 190 : if (list_length(siblings) > nchildren)
816 [ + - + - ]: 4 : ereport(ERROR,
817 : : errcode(ERRCODE_SYNTAX_ERROR),
818 : : errmsg("invalid JSON_TABLE plan clause"),
819 : : errdetail("PLAN clause contains some extra or duplicate sibling nodes."),
820 : : parser_errposition(pstate, plan ? plan->location : -1));
821 : 186 : }
|