Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * parse_jsontable.c
4 : * parsing of JSON_TABLE
5 : *
6 : * Portions Copyright (c) 1996-2024, 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_collation.h"
19 : #include "catalog/pg_type.h"
20 : #include "miscadmin.h"
21 : #include "nodes/makefuncs.h"
22 : #include "nodes/nodeFuncs.h"
23 : #include "optimizer/optimizer.h"
24 : #include "parser/parse_clause.h"
25 : #include "parser/parse_collate.h"
26 : #include "parser/parse_expr.h"
27 : #include "parser/parse_relation.h"
28 : #include "parser/parse_type.h"
29 : #include "utils/builtins.h"
30 : #include "utils/json.h"
31 : #include "utils/lsyscache.h"
32 :
33 : /* Context for transformJsonTableColumns() */
34 : typedef struct JsonTableParseContext
35 : {
36 : ParseState *pstate;
37 : JsonTable *jt;
38 : TableFunc *tf;
39 : List *pathNames; /* list of all path and columns names */
40 : int pathNameId; /* path name id counter */
41 : } JsonTableParseContext;
42 :
43 : static JsonTablePlan *transformJsonTableColumns(JsonTableParseContext *cxt,
44 : List *columns,
45 : List *passingArgs,
46 : JsonTablePathSpec *pathspec);
47 : static JsonTablePlan *transformJsonTableNestedColumns(JsonTableParseContext *cxt,
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(JsonTablePathSpec *pathspec,
55 : bool errorOnError,
56 : int colMin, int colMax,
57 : JsonTablePlan *childplan);
58 : static void CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
59 : List *columns);
60 : static bool LookupPathOrColumnName(JsonTableParseContext *cxt, char *name);
61 : static char *generateJsonTablePathName(JsonTableParseContext *cxt);
62 : static JsonTablePlan *makeJsonTableSiblingJoin(JsonTablePlan *lplan,
63 : JsonTablePlan *rplan);
64 :
65 : /*
66 : * transformJsonTable -
67 : * Transform a raw JsonTable into TableFunc
68 : *
69 : * Mainly, this transforms the JSON_TABLE() document-generating expression
70 : * (jt->context_item) and the column-generating expressions (jt->columns) to
71 : * populate TableFunc.docexpr and TableFunc.colvalexprs, respectively. Also,
72 : * the PASSING values (jt->passing) are transformed and added into
73 : * TableFunc.passingvalexprs.
74 : */
75 : ParseNamespaceItem *
76 428 : transformJsonTable(ParseState *pstate, JsonTable *jt)
77 : {
78 : TableFunc *tf;
79 : JsonFuncExpr *jfe;
80 : JsonExpr *je;
81 428 : JsonTablePathSpec *rootPathSpec = jt->pathspec;
82 : bool is_lateral;
83 428 : JsonTableParseContext cxt = {pstate};
84 :
85 : Assert(IsA(rootPathSpec->string, A_Const) &&
86 : castNode(A_Const, rootPathSpec->string)->val.node.type == T_String);
87 :
88 428 : if (jt->on_error &&
89 36 : jt->on_error->btype != JSON_BEHAVIOR_ERROR &&
90 18 : jt->on_error->btype != JSON_BEHAVIOR_EMPTY &&
91 18 : jt->on_error->btype != JSON_BEHAVIOR_EMPTY_ARRAY)
92 12 : ereport(ERROR,
93 : errcode(ERRCODE_SYNTAX_ERROR),
94 : errmsg("invalid ON ERROR behavior"),
95 : errdetail("Only EMPTY or ERROR is allowed in the top-level ON ERROR clause."),
96 : parser_errposition(pstate, jt->on_error->location));
97 :
98 416 : cxt.pathNameId = 0;
99 416 : if (rootPathSpec->name == NULL)
100 354 : rootPathSpec->name = generateJsonTablePathName(&cxt);
101 416 : cxt.pathNames = list_make1(rootPathSpec->name);
102 416 : CheckDuplicateColumnOrPathNames(&cxt, jt->columns);
103 :
104 : /*
105 : * We make lateral_only names of this level visible, whether or not the
106 : * RangeTableFunc is explicitly marked LATERAL. This is needed for SQL
107 : * spec compliance and seems useful on convenience grounds for all
108 : * functions in FROM.
109 : *
110 : * (LATERAL can't nest within a single pstate level, so we don't need
111 : * save/restore logic here.)
112 : */
113 : Assert(!pstate->p_lateral_active);
114 386 : pstate->p_lateral_active = true;
115 :
116 386 : tf = makeNode(TableFunc);
117 386 : tf->functype = TFT_JSON_TABLE;
118 :
119 : /*
120 : * Transform JsonFuncExpr representing the top JSON_TABLE context_item and
121 : * pathspec into a dummy JSON_TABLE_OP JsonExpr.
122 : */
123 386 : jfe = makeNode(JsonFuncExpr);
124 386 : jfe->op = JSON_TABLE_OP;
125 386 : jfe->context_item = jt->context_item;
126 386 : jfe->pathspec = (Node *) rootPathSpec->string;
127 386 : jfe->passing = jt->passing;
128 386 : jfe->on_empty = NULL;
129 386 : jfe->on_error = jt->on_error;
130 386 : jfe->location = jt->location;
131 386 : tf->docexpr = transformExpr(pstate, (Node *) jfe, EXPR_KIND_FROM_FUNCTION);
132 :
133 : /*
134 : * Create a JsonTablePlan that will generate row pattern that becomes
135 : * source data for JSON path expressions in jt->columns. This also adds
136 : * the columns' transformed JsonExpr nodes into tf->colvalexprs.
137 : */
138 386 : cxt.jt = jt;
139 386 : cxt.tf = tf;
140 386 : tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns,
141 : jt->passing,
142 : rootPathSpec);
143 :
144 : /*
145 : * Copy the transformed PASSING arguments into the TableFunc node, because
146 : * they are evaluated separately from the JsonExpr that we just put in
147 : * TableFunc.docexpr. JsonExpr.passing_values is still kept around for
148 : * get_json_table().
149 : */
150 344 : je = (JsonExpr *) tf->docexpr;
151 344 : tf->passingvalexprs = copyObject(je->passing_values);
152 :
153 344 : tf->ordinalitycol = -1; /* undefine ordinality column number */
154 344 : tf->location = jt->location;
155 :
156 344 : pstate->p_lateral_active = false;
157 :
158 : /*
159 : * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
160 : * there are any lateral cross-references in it.
161 : */
162 344 : is_lateral = jt->lateral || contain_vars_of_level((Node *) tf, 0);
163 :
164 344 : return addRangeTableEntryForTableFunc(pstate,
165 : tf, jt->alias, is_lateral, true);
166 : }
167 :
168 : /*
169 : * Check if a column / path name is duplicated in the given shared list of
170 : * names.
171 : */
172 : static void
173 688 : CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
174 : List *columns)
175 : {
176 : ListCell *lc1;
177 :
178 1868 : foreach(lc1, columns)
179 : {
180 1216 : JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc1));
181 :
182 1216 : if (jtc->coltype == JTC_NESTED)
183 : {
184 290 : if (jtc->pathspec->name)
185 : {
186 146 : if (LookupPathOrColumnName(cxt, jtc->pathspec->name))
187 18 : ereport(ERROR,
188 : errcode(ERRCODE_DUPLICATE_ALIAS),
189 : errmsg("duplicate JSON_TABLE column or path name: %s",
190 : jtc->pathspec->name),
191 : parser_errposition(cxt->pstate,
192 : jtc->pathspec->name_location));
193 128 : cxt->pathNames = lappend(cxt->pathNames, jtc->pathspec->name);
194 : }
195 :
196 272 : CheckDuplicateColumnOrPathNames(cxt, jtc->columns);
197 : }
198 : else
199 : {
200 926 : if (LookupPathOrColumnName(cxt, jtc->name))
201 12 : ereport(ERROR,
202 : errcode(ERRCODE_DUPLICATE_ALIAS),
203 : errmsg("duplicate JSON_TABLE column or path name: %s",
204 : jtc->name),
205 : parser_errposition(cxt->pstate, jtc->location));
206 914 : cxt->pathNames = lappend(cxt->pathNames, jtc->name);
207 : }
208 : }
209 652 : }
210 :
211 : /*
212 : * Lookup a column/path name in the given name list, returning true if already
213 : * there.
214 : */
215 : static bool
216 1072 : LookupPathOrColumnName(JsonTableParseContext *cxt, char *name)
217 : {
218 : ListCell *lc;
219 :
220 4286 : foreach(lc, cxt->pathNames)
221 : {
222 3244 : if (strcmp(name, (const char *) lfirst(lc)) == 0)
223 30 : return true;
224 : }
225 :
226 1042 : return false;
227 : }
228 :
229 : /* Generate a new unique JSON_TABLE path name. */
230 : static char *
231 492 : generateJsonTablePathName(JsonTableParseContext *cxt)
232 : {
233 : char namebuf[32];
234 492 : char *name = namebuf;
235 :
236 492 : snprintf(namebuf, sizeof(namebuf), "json_table_path_%d",
237 492 : cxt->pathNameId++);
238 :
239 492 : name = pstrdup(name);
240 492 : cxt->pathNames = lappend(cxt->pathNames, name);
241 :
242 492 : return name;
243 : }
244 :
245 : /*
246 : * Create a JsonTablePlan that will supply the source row for 'columns'
247 : * using 'pathspec' and append the columns' transformed JsonExpr nodes and
248 : * their type/collation information to cxt->tf.
249 : */
250 : static JsonTablePlan *
251 646 : transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
252 : List *passingArgs,
253 : JsonTablePathSpec *pathspec)
254 : {
255 646 : ParseState *pstate = cxt->pstate;
256 646 : JsonTable *jt = cxt->jt;
257 646 : TableFunc *tf = cxt->tf;
258 : ListCell *col;
259 646 : bool ordinality_found = false;
260 670 : bool errorOnError = jt->on_error &&
261 24 : jt->on_error->btype == JSON_BEHAVIOR_ERROR;
262 646 : Oid contextItemTypid = exprType(tf->docexpr);
263 : int colMin,
264 : colMax;
265 : JsonTablePlan *childplan;
266 :
267 : /* Start of column range */
268 646 : colMin = list_length(tf->colvalexprs);
269 :
270 1748 : foreach(col, columns)
271 : {
272 1144 : JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col));
273 : Oid typid;
274 : int32 typmod;
275 1144 : Oid typcoll = InvalidOid;
276 : Node *colexpr;
277 :
278 1144 : if (rawc->coltype != JTC_NESTED)
279 : {
280 : Assert(rawc->name);
281 884 : tf->colnames = lappend(tf->colnames,
282 884 : makeString(pstrdup(rawc->name)));
283 : }
284 :
285 : /*
286 : * Determine the type and typmod for the new column. FOR ORDINALITY
287 : * columns are INTEGER by standard; the others are user-specified.
288 : */
289 1144 : switch (rawc->coltype)
290 : {
291 84 : case JTC_FOR_ORDINALITY:
292 84 : if (ordinality_found)
293 6 : ereport(ERROR,
294 : (errcode(ERRCODE_SYNTAX_ERROR),
295 : errmsg("cannot use more than one FOR ORDINALITY column"),
296 : parser_errposition(pstate, rawc->location)));
297 78 : ordinality_found = true;
298 78 : colexpr = NULL;
299 78 : typid = INT4OID;
300 78 : typmod = -1;
301 78 : break;
302 :
303 608 : case JTC_REGULAR:
304 608 : typenameTypeIdAndMod(pstate, rawc->typeName, &typid, &typmod);
305 :
306 : /*
307 : * Use JTC_FORMATTED so as to use JSON_QUERY for this column
308 : * if the specified type is one that's better handled using
309 : * JSON_QUERY() or if non-default WRAPPER or QUOTES behavior
310 : * is specified.
311 : */
312 608 : if (isCompositeType(typid) ||
313 476 : rawc->quotes != JS_QUOTES_UNSPEC ||
314 440 : rawc->wrapper != JSW_UNSPEC)
315 168 : rawc->coltype = JTC_FORMATTED;
316 :
317 : /* FALLTHROUGH */
318 : case JTC_FORMATTED:
319 : case JTC_EXISTS:
320 : {
321 : JsonFuncExpr *jfe;
322 800 : CaseTestExpr *param = makeNode(CaseTestExpr);
323 :
324 800 : param->collation = InvalidOid;
325 800 : param->typeId = contextItemTypid;
326 800 : param->typeMod = -1;
327 :
328 800 : jfe = transformJsonTableColumn(rawc, (Node *) param,
329 : passingArgs);
330 :
331 800 : colexpr = transformExpr(pstate, (Node *) jfe,
332 : EXPR_KIND_FROM_FUNCTION);
333 764 : assign_expr_collations(pstate, colexpr);
334 :
335 764 : typid = exprType(colexpr);
336 764 : typmod = exprTypmod(colexpr);
337 764 : typcoll = exprCollation(colexpr);
338 764 : break;
339 : }
340 :
341 260 : case JTC_NESTED:
342 260 : continue;
343 :
344 0 : default:
345 0 : elog(ERROR, "unknown JSON_TABLE column type: %d", (int) rawc->coltype);
346 : break;
347 : }
348 :
349 842 : tf->coltypes = lappend_oid(tf->coltypes, typid);
350 842 : tf->coltypmods = lappend_int(tf->coltypmods, typmod);
351 842 : tf->colcollations = lappend_oid(tf->colcollations, typcoll);
352 842 : tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
353 : }
354 :
355 : /* End of column range. */
356 604 : if (list_length(tf->colvalexprs) == colMin)
357 : {
358 : /* No columns in this Scan beside the nested ones. */
359 104 : colMax = colMin = -1;
360 : }
361 : else
362 500 : colMax = list_length(tf->colvalexprs) - 1;
363 :
364 : /* Recursively transform nested columns */
365 604 : childplan = transformJsonTableNestedColumns(cxt, passingArgs, columns);
366 :
367 : /* Create a "parent" scan responsible for all columns handled above. */
368 604 : return makeJsonTablePathScan(pathspec, errorOnError, colMin, colMax,
369 : childplan);
370 : }
371 :
372 : /*
373 : * Check if the type is "composite" for the purpose of checking whether to use
374 : * JSON_VALUE() or JSON_QUERY() for a given JsonTableColumn.
375 : */
376 : static bool
377 644 : isCompositeType(Oid typid)
378 : {
379 644 : char typtype = get_typtype(typid);
380 :
381 602 : return typid == JSONOID ||
382 566 : typid == JSONBOID ||
383 566 : typid == RECORDOID ||
384 1084 : type_is_array(typid) ||
385 1282 : typtype == TYPTYPE_COMPOSITE ||
386 : /* domain over one of the above? */
387 36 : (typtype == TYPTYPE_DOMAIN &&
388 36 : isCompositeType(getBaseType(typid)));
389 : }
390 :
391 : /*
392 : * Transform JSON_TABLE column definition into a JsonFuncExpr
393 : * This turns:
394 : * - regular column into JSON_VALUE()
395 : * - FORMAT JSON column into JSON_QUERY()
396 : * - EXISTS column into JSON_EXISTS()
397 : */
398 : static JsonFuncExpr *
399 800 : transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
400 : List *passingArgs)
401 : {
402 : Node *pathspec;
403 800 : JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
404 :
405 800 : if (jtc->coltype == JTC_REGULAR)
406 440 : jfexpr->op = JSON_VALUE_OP;
407 360 : else if (jtc->coltype == JTC_EXISTS)
408 84 : jfexpr->op = JSON_EXISTS_OP;
409 : else
410 276 : jfexpr->op = JSON_QUERY_OP;
411 :
412 : /* Pass the column name so any runtime JsonExpr errors can print it. */
413 : Assert(jtc->name != NULL);
414 800 : jfexpr->column_name = pstrdup(jtc->name);
415 :
416 800 : jfexpr->context_item = makeJsonValueExpr((Expr *) contextItemExpr, NULL,
417 : makeJsonFormat(JS_FORMAT_DEFAULT,
418 : JS_ENC_DEFAULT,
419 : -1));
420 800 : if (jtc->pathspec)
421 720 : pathspec = (Node *) jtc->pathspec->string;
422 : else
423 : {
424 : /* Construct default path as '$."column_name"' */
425 : StringInfoData path;
426 :
427 80 : initStringInfo(&path);
428 :
429 80 : appendStringInfoString(&path, "$.");
430 80 : escape_json(&path, jtc->name);
431 :
432 80 : pathspec = makeStringConst(path.data, -1);
433 : }
434 800 : jfexpr->pathspec = pathspec;
435 800 : jfexpr->passing = passingArgs;
436 800 : jfexpr->output = makeNode(JsonOutput);
437 800 : jfexpr->output->typeName = jtc->typeName;
438 800 : jfexpr->output->returning = makeNode(JsonReturning);
439 800 : jfexpr->output->returning->format = jtc->format;
440 800 : jfexpr->on_empty = jtc->on_empty;
441 800 : jfexpr->on_error = jtc->on_error;
442 800 : jfexpr->quotes = jtc->quotes;
443 800 : jfexpr->wrapper = jtc->wrapper;
444 800 : jfexpr->location = jtc->location;
445 :
446 800 : return jfexpr;
447 : }
448 :
449 : /*
450 : * Recursively transform nested columns and create child plan(s) that will be
451 : * used to evaluate their row patterns.
452 : */
453 : static JsonTablePlan *
454 604 : transformJsonTableNestedColumns(JsonTableParseContext *cxt,
455 : List *passingArgs,
456 : List *columns)
457 : {
458 604 : JsonTablePlan *plan = NULL;
459 : ListCell *lc;
460 :
461 : /*
462 : * If there are multiple NESTED COLUMNS clauses in 'columns', their
463 : * respective plans will be combined using a "sibling join" plan, which
464 : * effectively does a UNION of the sets of rows coming from each nested
465 : * plan.
466 : */
467 1700 : foreach(lc, columns)
468 : {
469 1096 : JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
470 : JsonTablePlan *nested;
471 :
472 1096 : if (jtc->coltype != JTC_NESTED)
473 836 : continue;
474 :
475 260 : if (jtc->pathspec->name == NULL)
476 138 : jtc->pathspec->name = generateJsonTablePathName(cxt);
477 :
478 260 : nested = transformJsonTableColumns(cxt, jtc->columns, passingArgs,
479 : jtc->pathspec);
480 :
481 260 : if (plan)
482 90 : plan = makeJsonTableSiblingJoin(plan, nested);
483 : else
484 170 : plan = nested;
485 : }
486 :
487 604 : return plan;
488 : }
489 :
490 : /*
491 : * Create a JsonTablePlan for given path and ON ERROR behavior.
492 : *
493 : * colMin and colMin give the range of columns computed by this scan in the
494 : * global flat list of column expressions that will be passed to the
495 : * JSON_TABLE's TableFunc. Both are -1 when all of columns are nested and
496 : * thus computed by 'childplan'.
497 : */
498 : static JsonTablePlan *
499 604 : makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
500 : int colMin, int colMax,
501 : JsonTablePlan *childplan)
502 : {
503 604 : JsonTablePathScan *scan = makeNode(JsonTablePathScan);
504 : char *pathstring;
505 : Const *value;
506 :
507 : Assert(IsA(pathspec->string, A_Const));
508 604 : pathstring = castNode(A_Const, pathspec->string)->val.sval.sval;
509 604 : value = makeConst(JSONPATHOID, -1, InvalidOid, -1,
510 : DirectFunctionCall1(jsonpath_in,
511 : CStringGetDatum(pathstring)),
512 : false, false);
513 :
514 604 : scan->plan.type = T_JsonTablePathScan;
515 604 : scan->path = makeJsonTablePath(value, pathspec->name);
516 604 : scan->errorOnError = errorOnError;
517 :
518 604 : scan->child = childplan;
519 :
520 604 : scan->colMin = colMin;
521 604 : scan->colMax = colMax;
522 :
523 604 : return (JsonTablePlan *) scan;
524 : }
525 :
526 : /*
527 : * Create a JsonTablePlan that will perform a join of the rows coming from
528 : * 'lplan' and 'rplan'.
529 : *
530 : * The default way of "joining" the rows is to perform a UNION between the
531 : * sets of rows from 'lplan' and 'rplan'.
532 : */
533 : static JsonTablePlan *
534 90 : makeJsonTableSiblingJoin(JsonTablePlan *lplan, JsonTablePlan *rplan)
535 : {
536 90 : JsonTableSiblingJoin *join = makeNode(JsonTableSiblingJoin);
537 :
538 90 : join->plan.type = T_JsonTableSiblingJoin;
539 90 : join->lplan = lplan;
540 90 : join->rplan = rplan;
541 :
542 90 : return (JsonTablePlan *) join;
543 : }
|