Coverage Report

Created: 2025-06-13 06:06

/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
}