/src/postgres/src/backend/executor/nodeTableFuncscan.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * nodeTableFuncscan.c |
4 | | * Support routines for scanning RangeTableFunc (XMLTABLE like functions). |
5 | | * |
6 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * |
10 | | * IDENTIFICATION |
11 | | * src/backend/executor/nodeTableFuncscan.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | /* |
16 | | * INTERFACE ROUTINES |
17 | | * ExecTableFuncScan scans a function. |
18 | | * ExecFunctionNext retrieve next tuple in sequential order. |
19 | | * ExecInitTableFuncScan creates and initializes a TableFuncscan node. |
20 | | * ExecEndTableFuncScan releases any storage allocated. |
21 | | * ExecReScanTableFuncScan rescans the function |
22 | | */ |
23 | | #include "postgres.h" |
24 | | |
25 | | #include "executor/executor.h" |
26 | | #include "executor/nodeTableFuncscan.h" |
27 | | #include "executor/tablefunc.h" |
28 | | #include "miscadmin.h" |
29 | | #include "nodes/execnodes.h" |
30 | | #include "utils/builtins.h" |
31 | | #include "utils/jsonpath.h" |
32 | | #include "utils/lsyscache.h" |
33 | | #include "utils/memutils.h" |
34 | | #include "utils/xml.h" |
35 | | |
36 | | static TupleTableSlot *TableFuncNext(TableFuncScanState *node); |
37 | | static bool TableFuncRecheck(TableFuncScanState *node, TupleTableSlot *slot); |
38 | | |
39 | | static void tfuncFetchRows(TableFuncScanState *tstate, ExprContext *econtext); |
40 | | static void tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc); |
41 | | static void tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext); |
42 | | |
43 | | /* ---------------------------------------------------------------- |
44 | | * Scan Support |
45 | | * ---------------------------------------------------------------- |
46 | | */ |
47 | | /* ---------------------------------------------------------------- |
48 | | * TableFuncNext |
49 | | * |
50 | | * This is a workhorse for ExecTableFuncScan |
51 | | * ---------------------------------------------------------------- |
52 | | */ |
53 | | static TupleTableSlot * |
54 | | TableFuncNext(TableFuncScanState *node) |
55 | 0 | { |
56 | 0 | TupleTableSlot *scanslot; |
57 | |
|
58 | 0 | scanslot = node->ss.ss_ScanTupleSlot; |
59 | | |
60 | | /* |
61 | | * If first time through, read all tuples from function and put them in a |
62 | | * tuplestore. Subsequent calls just fetch tuples from tuplestore. |
63 | | */ |
64 | 0 | if (node->tupstore == NULL) |
65 | 0 | tfuncFetchRows(node, node->ss.ps.ps_ExprContext); |
66 | | |
67 | | /* |
68 | | * Get the next tuple from tuplestore. |
69 | | */ |
70 | 0 | (void) tuplestore_gettupleslot(node->tupstore, |
71 | 0 | true, |
72 | 0 | false, |
73 | 0 | scanslot); |
74 | 0 | return scanslot; |
75 | 0 | } |
76 | | |
77 | | /* |
78 | | * TableFuncRecheck -- access method routine to recheck a tuple in EvalPlanQual |
79 | | */ |
80 | | static bool |
81 | | TableFuncRecheck(TableFuncScanState *node, TupleTableSlot *slot) |
82 | 0 | { |
83 | | /* nothing to check */ |
84 | 0 | return true; |
85 | 0 | } |
86 | | |
87 | | /* ---------------------------------------------------------------- |
88 | | * ExecTableFuncScan(node) |
89 | | * |
90 | | * Scans the function sequentially and returns the next qualifying |
91 | | * tuple. |
92 | | * We call the ExecScan() routine and pass it the appropriate |
93 | | * access method functions. |
94 | | * ---------------------------------------------------------------- |
95 | | */ |
96 | | static TupleTableSlot * |
97 | | ExecTableFuncScan(PlanState *pstate) |
98 | 0 | { |
99 | 0 | TableFuncScanState *node = castNode(TableFuncScanState, pstate); |
100 | |
|
101 | 0 | return ExecScan(&node->ss, |
102 | 0 | (ExecScanAccessMtd) TableFuncNext, |
103 | 0 | (ExecScanRecheckMtd) TableFuncRecheck); |
104 | 0 | } |
105 | | |
106 | | /* ---------------------------------------------------------------- |
107 | | * ExecInitTableFuncScan |
108 | | * ---------------------------------------------------------------- |
109 | | */ |
110 | | TableFuncScanState * |
111 | | ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags) |
112 | 0 | { |
113 | 0 | TableFuncScanState *scanstate; |
114 | 0 | TableFunc *tf = node->tablefunc; |
115 | 0 | TupleDesc tupdesc; |
116 | 0 | int i; |
117 | | |
118 | | /* check for unsupported flags */ |
119 | 0 | Assert(!(eflags & EXEC_FLAG_MARK)); |
120 | | |
121 | | /* |
122 | | * TableFuncscan should not have any children. |
123 | | */ |
124 | 0 | Assert(outerPlan(node) == NULL); |
125 | 0 | Assert(innerPlan(node) == NULL); |
126 | | |
127 | | /* |
128 | | * create new ScanState for node |
129 | | */ |
130 | 0 | scanstate = makeNode(TableFuncScanState); |
131 | 0 | scanstate->ss.ps.plan = (Plan *) node; |
132 | 0 | scanstate->ss.ps.state = estate; |
133 | 0 | scanstate->ss.ps.ExecProcNode = ExecTableFuncScan; |
134 | | |
135 | | /* |
136 | | * Miscellaneous initialization |
137 | | * |
138 | | * create expression context for node |
139 | | */ |
140 | 0 | ExecAssignExprContext(estate, &scanstate->ss.ps); |
141 | | |
142 | | /* |
143 | | * initialize source tuple type |
144 | | */ |
145 | 0 | tupdesc = BuildDescFromLists(tf->colnames, |
146 | 0 | tf->coltypes, |
147 | 0 | tf->coltypmods, |
148 | 0 | tf->colcollations); |
149 | | /* and the corresponding scan slot */ |
150 | 0 | ExecInitScanTupleSlot(estate, &scanstate->ss, tupdesc, |
151 | 0 | &TTSOpsMinimalTuple); |
152 | | |
153 | | /* |
154 | | * Initialize result type and projection. |
155 | | */ |
156 | 0 | ExecInitResultTypeTL(&scanstate->ss.ps); |
157 | 0 | ExecAssignScanProjectionInfo(&scanstate->ss); |
158 | | |
159 | | /* |
160 | | * initialize child expressions |
161 | | */ |
162 | 0 | scanstate->ss.ps.qual = |
163 | 0 | ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps); |
164 | | |
165 | | /* Only XMLTABLE and JSON_TABLE are supported currently */ |
166 | 0 | scanstate->routine = |
167 | 0 | tf->functype == TFT_XMLTABLE ? &XmlTableRoutine : &JsonbTableRoutine; |
168 | |
|
169 | 0 | scanstate->perTableCxt = |
170 | 0 | AllocSetContextCreate(CurrentMemoryContext, |
171 | 0 | "TableFunc per value context", |
172 | 0 | ALLOCSET_DEFAULT_SIZES); |
173 | 0 | scanstate->opaque = NULL; /* initialized at runtime */ |
174 | |
|
175 | 0 | scanstate->ns_names = tf->ns_names; |
176 | |
|
177 | 0 | scanstate->ns_uris = |
178 | 0 | ExecInitExprList(tf->ns_uris, (PlanState *) scanstate); |
179 | 0 | scanstate->docexpr = |
180 | 0 | ExecInitExpr((Expr *) tf->docexpr, (PlanState *) scanstate); |
181 | 0 | scanstate->rowexpr = |
182 | 0 | ExecInitExpr((Expr *) tf->rowexpr, (PlanState *) scanstate); |
183 | 0 | scanstate->colexprs = |
184 | 0 | ExecInitExprList(tf->colexprs, (PlanState *) scanstate); |
185 | 0 | scanstate->coldefexprs = |
186 | 0 | ExecInitExprList(tf->coldefexprs, (PlanState *) scanstate); |
187 | 0 | scanstate->colvalexprs = |
188 | 0 | ExecInitExprList(tf->colvalexprs, (PlanState *) scanstate); |
189 | 0 | scanstate->passingvalexprs = |
190 | 0 | ExecInitExprList(tf->passingvalexprs, (PlanState *) scanstate); |
191 | |
|
192 | 0 | scanstate->notnulls = tf->notnulls; |
193 | | |
194 | | /* these are allocated now and initialized later */ |
195 | 0 | scanstate->in_functions = palloc(sizeof(FmgrInfo) * tupdesc->natts); |
196 | 0 | scanstate->typioparams = palloc(sizeof(Oid) * tupdesc->natts); |
197 | | |
198 | | /* |
199 | | * Fill in the necessary fmgr infos. |
200 | | */ |
201 | 0 | for (i = 0; i < tupdesc->natts; i++) |
202 | 0 | { |
203 | 0 | Oid in_funcid; |
204 | |
|
205 | 0 | getTypeInputInfo(TupleDescAttr(tupdesc, i)->atttypid, |
206 | 0 | &in_funcid, &scanstate->typioparams[i]); |
207 | 0 | fmgr_info(in_funcid, &scanstate->in_functions[i]); |
208 | 0 | } |
209 | |
|
210 | 0 | return scanstate; |
211 | 0 | } |
212 | | |
213 | | /* ---------------------------------------------------------------- |
214 | | * ExecEndTableFuncScan |
215 | | * |
216 | | * frees any storage allocated through C routines. |
217 | | * ---------------------------------------------------------------- |
218 | | */ |
219 | | void |
220 | | ExecEndTableFuncScan(TableFuncScanState *node) |
221 | 0 | { |
222 | | /* |
223 | | * Release tuplestore resources |
224 | | */ |
225 | 0 | if (node->tupstore != NULL) |
226 | 0 | tuplestore_end(node->tupstore); |
227 | 0 | node->tupstore = NULL; |
228 | 0 | } |
229 | | |
230 | | /* ---------------------------------------------------------------- |
231 | | * ExecReScanTableFuncScan |
232 | | * |
233 | | * Rescans the relation. |
234 | | * ---------------------------------------------------------------- |
235 | | */ |
236 | | void |
237 | | ExecReScanTableFuncScan(TableFuncScanState *node) |
238 | 0 | { |
239 | 0 | Bitmapset *chgparam = node->ss.ps.chgParam; |
240 | |
|
241 | 0 | if (node->ss.ps.ps_ResultTupleSlot) |
242 | 0 | ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); |
243 | 0 | ExecScanReScan(&node->ss); |
244 | | |
245 | | /* |
246 | | * Recompute when parameters are changed. |
247 | | */ |
248 | 0 | if (chgparam) |
249 | 0 | { |
250 | 0 | if (node->tupstore != NULL) |
251 | 0 | { |
252 | 0 | tuplestore_end(node->tupstore); |
253 | 0 | node->tupstore = NULL; |
254 | 0 | } |
255 | 0 | } |
256 | |
|
257 | 0 | if (node->tupstore != NULL) |
258 | 0 | tuplestore_rescan(node->tupstore); |
259 | 0 | } |
260 | | |
261 | | /* ---------------------------------------------------------------- |
262 | | * tfuncFetchRows |
263 | | * |
264 | | * Read rows from a TableFunc producer |
265 | | * ---------------------------------------------------------------- |
266 | | */ |
267 | | static void |
268 | | tfuncFetchRows(TableFuncScanState *tstate, ExprContext *econtext) |
269 | 0 | { |
270 | 0 | const TableFuncRoutine *routine = tstate->routine; |
271 | 0 | MemoryContext oldcxt; |
272 | 0 | Datum value; |
273 | 0 | bool isnull; |
274 | |
|
275 | 0 | Assert(tstate->opaque == NULL); |
276 | | |
277 | | /* build tuplestore for the result */ |
278 | 0 | oldcxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); |
279 | 0 | tstate->tupstore = tuplestore_begin_heap(false, false, work_mem); |
280 | | |
281 | | /* |
282 | | * Each call to fetch a new set of rows - of which there may be very many |
283 | | * if XMLTABLE or JSON_TABLE is being used in a lateral join - will |
284 | | * allocate a possibly substantial amount of memory, so we cannot use the |
285 | | * per-query context here. perTableCxt now serves the same function as |
286 | | * "argcontext" does in FunctionScan - a place to store per-one-call (i.e. |
287 | | * one result table) lifetime data (as opposed to per-query or |
288 | | * per-result-tuple). |
289 | | */ |
290 | 0 | MemoryContextSwitchTo(tstate->perTableCxt); |
291 | |
|
292 | 0 | PG_TRY(); |
293 | 0 | { |
294 | 0 | routine->InitOpaque(tstate, |
295 | 0 | tstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor->natts); |
296 | | |
297 | | /* |
298 | | * If evaluating the document expression returns NULL, the table |
299 | | * expression is empty and we return immediately. |
300 | | */ |
301 | 0 | value = ExecEvalExpr(tstate->docexpr, econtext, &isnull); |
302 | |
|
303 | 0 | if (!isnull) |
304 | 0 | { |
305 | | /* otherwise, pass the document value to the table builder */ |
306 | 0 | tfuncInitialize(tstate, econtext, value); |
307 | | |
308 | | /* initialize ordinality counter */ |
309 | 0 | tstate->ordinal = 1; |
310 | | |
311 | | /* Load all rows into the tuplestore, and we're done */ |
312 | 0 | tfuncLoadRows(tstate, econtext); |
313 | 0 | } |
314 | 0 | } |
315 | 0 | PG_CATCH(); |
316 | 0 | { |
317 | 0 | if (tstate->opaque != NULL) |
318 | 0 | routine->DestroyOpaque(tstate); |
319 | 0 | PG_RE_THROW(); |
320 | 0 | } |
321 | 0 | PG_END_TRY(); |
322 | | |
323 | | /* clean up and return to original memory context */ |
324 | |
|
325 | 0 | if (tstate->opaque != NULL) |
326 | 0 | { |
327 | 0 | routine->DestroyOpaque(tstate); |
328 | 0 | tstate->opaque = NULL; |
329 | 0 | } |
330 | |
|
331 | 0 | MemoryContextSwitchTo(oldcxt); |
332 | 0 | MemoryContextReset(tstate->perTableCxt); |
333 | 0 | } |
334 | | |
335 | | /* |
336 | | * Fill in namespace declarations, the row filter, and column filters in a |
337 | | * table expression builder context. |
338 | | */ |
339 | | static void |
340 | | tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc) |
341 | 0 | { |
342 | 0 | const TableFuncRoutine *routine = tstate->routine; |
343 | 0 | TupleDesc tupdesc; |
344 | 0 | ListCell *lc1, |
345 | 0 | *lc2; |
346 | 0 | bool isnull; |
347 | 0 | int colno; |
348 | 0 | Datum value; |
349 | 0 | int ordinalitycol = |
350 | 0 | ((TableFuncScan *) (tstate->ss.ps.plan))->tablefunc->ordinalitycol; |
351 | | |
352 | | /* |
353 | | * Install the document as a possibly-toasted Datum into the tablefunc |
354 | | * context. |
355 | | */ |
356 | 0 | routine->SetDocument(tstate, doc); |
357 | | |
358 | | /* Evaluate namespace specifications */ |
359 | 0 | forboth(lc1, tstate->ns_uris, lc2, tstate->ns_names) |
360 | 0 | { |
361 | 0 | ExprState *expr = (ExprState *) lfirst(lc1); |
362 | 0 | String *ns_node = lfirst_node(String, lc2); |
363 | 0 | char *ns_uri; |
364 | 0 | char *ns_name; |
365 | |
|
366 | 0 | value = ExecEvalExpr((ExprState *) expr, econtext, &isnull); |
367 | 0 | if (isnull) |
368 | 0 | ereport(ERROR, |
369 | 0 | (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), |
370 | 0 | errmsg("namespace URI must not be null"))); |
371 | 0 | ns_uri = TextDatumGetCString(value); |
372 | | |
373 | | /* DEFAULT is passed down to SetNamespace as NULL */ |
374 | 0 | ns_name = ns_node ? strVal(ns_node) : NULL; |
375 | |
|
376 | 0 | routine->SetNamespace(tstate, ns_name, ns_uri); |
377 | 0 | } |
378 | | |
379 | | /* |
380 | | * Install the row filter expression, if any, into the table builder |
381 | | * context. |
382 | | */ |
383 | 0 | if (routine->SetRowFilter) |
384 | 0 | { |
385 | 0 | value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull); |
386 | 0 | if (isnull) |
387 | 0 | ereport(ERROR, |
388 | 0 | (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), |
389 | 0 | errmsg("row filter expression must not be null"))); |
390 | | |
391 | 0 | routine->SetRowFilter(tstate, TextDatumGetCString(value)); |
392 | 0 | } |
393 | | |
394 | | /* |
395 | | * Install the column filter expressions into the table builder context. |
396 | | * If an expression is given, use that; otherwise the column name itself |
397 | | * is the column filter. |
398 | | */ |
399 | 0 | colno = 0; |
400 | 0 | tupdesc = tstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor; |
401 | 0 | foreach(lc1, tstate->colexprs) |
402 | 0 | { |
403 | 0 | char *colfilter; |
404 | 0 | Form_pg_attribute att = TupleDescAttr(tupdesc, colno); |
405 | |
|
406 | 0 | if (colno != ordinalitycol) |
407 | 0 | { |
408 | 0 | ExprState *colexpr = lfirst(lc1); |
409 | |
|
410 | 0 | if (colexpr != NULL) |
411 | 0 | { |
412 | 0 | value = ExecEvalExpr(colexpr, econtext, &isnull); |
413 | 0 | if (isnull) |
414 | 0 | ereport(ERROR, |
415 | 0 | (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), |
416 | 0 | errmsg("column filter expression must not be null"), |
417 | 0 | errdetail("Filter for column \"%s\" is null.", |
418 | 0 | NameStr(att->attname)))); |
419 | 0 | colfilter = TextDatumGetCString(value); |
420 | 0 | } |
421 | 0 | else |
422 | 0 | colfilter = NameStr(att->attname); |
423 | | |
424 | 0 | routine->SetColumnFilter(tstate, colfilter, colno); |
425 | 0 | } |
426 | | |
427 | 0 | colno++; |
428 | 0 | } |
429 | 0 | } |
430 | | |
431 | | /* |
432 | | * Load all the rows from the TableFunc table builder into a tuplestore. |
433 | | */ |
434 | | static void |
435 | | tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext) |
436 | 0 | { |
437 | 0 | const TableFuncRoutine *routine = tstate->routine; |
438 | 0 | TupleTableSlot *slot = tstate->ss.ss_ScanTupleSlot; |
439 | 0 | TupleDesc tupdesc = slot->tts_tupleDescriptor; |
440 | 0 | Datum *values = slot->tts_values; |
441 | 0 | bool *nulls = slot->tts_isnull; |
442 | 0 | int natts = tupdesc->natts; |
443 | 0 | MemoryContext oldcxt; |
444 | 0 | int ordinalitycol; |
445 | |
|
446 | 0 | ordinalitycol = |
447 | 0 | ((TableFuncScan *) (tstate->ss.ps.plan))->tablefunc->ordinalitycol; |
448 | | |
449 | | /* |
450 | | * We need a short-lived memory context that we can clean up each time |
451 | | * around the loop, to avoid wasting space. Our default per-tuple context |
452 | | * is fine for the job, since we won't have used it for anything yet in |
453 | | * this tuple cycle. |
454 | | */ |
455 | 0 | oldcxt = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); |
456 | | |
457 | | /* |
458 | | * Keep requesting rows from the table builder until there aren't any. |
459 | | */ |
460 | 0 | while (routine->FetchRow(tstate)) |
461 | 0 | { |
462 | 0 | ListCell *cell = list_head(tstate->coldefexprs); |
463 | 0 | int colno; |
464 | |
|
465 | 0 | CHECK_FOR_INTERRUPTS(); |
466 | |
|
467 | 0 | ExecClearTuple(tstate->ss.ss_ScanTupleSlot); |
468 | | |
469 | | /* |
470 | | * Obtain the value of each column for this row, installing them into |
471 | | * the slot; then add the tuple to the tuplestore. |
472 | | */ |
473 | 0 | for (colno = 0; colno < natts; colno++) |
474 | 0 | { |
475 | 0 | Form_pg_attribute att = TupleDescAttr(tupdesc, colno); |
476 | |
|
477 | 0 | if (colno == ordinalitycol) |
478 | 0 | { |
479 | | /* Fast path for ordinality column */ |
480 | 0 | values[colno] = Int32GetDatum(tstate->ordinal++); |
481 | 0 | nulls[colno] = false; |
482 | 0 | } |
483 | 0 | else |
484 | 0 | { |
485 | 0 | bool isnull; |
486 | |
|
487 | 0 | values[colno] = routine->GetValue(tstate, |
488 | 0 | colno, |
489 | 0 | att->atttypid, |
490 | 0 | att->atttypmod, |
491 | 0 | &isnull); |
492 | | |
493 | | /* No value? Evaluate and apply the default, if any */ |
494 | 0 | if (isnull && cell != NULL) |
495 | 0 | { |
496 | 0 | ExprState *coldefexpr = (ExprState *) lfirst(cell); |
497 | |
|
498 | 0 | if (coldefexpr != NULL) |
499 | 0 | values[colno] = ExecEvalExpr(coldefexpr, econtext, |
500 | 0 | &isnull); |
501 | 0 | } |
502 | | |
503 | | /* Verify a possible NOT NULL constraint */ |
504 | 0 | if (isnull && bms_is_member(colno, tstate->notnulls)) |
505 | 0 | ereport(ERROR, |
506 | 0 | (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), |
507 | 0 | errmsg("null is not allowed in column \"%s\"", |
508 | 0 | NameStr(att->attname)))); |
509 | | |
510 | 0 | nulls[colno] = isnull; |
511 | 0 | } |
512 | | |
513 | | /* advance list of default expressions */ |
514 | 0 | if (cell != NULL) |
515 | 0 | cell = lnext(tstate->coldefexprs, cell); |
516 | 0 | } |
517 | | |
518 | 0 | tuplestore_putvalues(tstate->tupstore, tupdesc, values, nulls); |
519 | |
|
520 | 0 | MemoryContextReset(econtext->ecxt_per_tuple_memory); |
521 | 0 | } |
522 | | |
523 | 0 | MemoryContextSwitchTo(oldcxt); |
524 | 0 | } |