Coverage Report

Created: 2025-06-15 06:31

/src/postgres/src/backend/executor/nodeProjectSet.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * nodeProjectSet.c
4
 *    support for evaluating targetlists containing set-returning functions
5
 *
6
 * DESCRIPTION
7
 *
8
 *    ProjectSet nodes are inserted by the planner to evaluate set-returning
9
 *    functions in the targetlist.  It's guaranteed that all set-returning
10
 *    functions are directly at the top level of the targetlist, i.e. they
11
 *    can't be inside more-complex expressions.  If that'd otherwise be
12
 *    the case, the planner adds additional ProjectSet nodes.
13
 *
14
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
15
 * Portions Copyright (c) 1994, Regents of the University of California
16
 *
17
 * IDENTIFICATION
18
 *    src/backend/executor/nodeProjectSet.c
19
 *
20
 *-------------------------------------------------------------------------
21
 */
22
23
#include "postgres.h"
24
25
#include "executor/executor.h"
26
#include "executor/nodeProjectSet.h"
27
#include "miscadmin.h"
28
#include "nodes/nodeFuncs.h"
29
30
31
static TupleTableSlot *ExecProjectSRF(ProjectSetState *node, bool continuing);
32
33
34
/* ----------------------------------------------------------------
35
 *    ExecProjectSet(node)
36
 *
37
 *    Return tuples after evaluating the targetlist (which contains set
38
 *    returning functions).
39
 * ----------------------------------------------------------------
40
 */
41
static TupleTableSlot *
42
ExecProjectSet(PlanState *pstate)
43
0
{
44
0
  ProjectSetState *node = castNode(ProjectSetState, pstate);
45
0
  TupleTableSlot *outerTupleSlot;
46
0
  TupleTableSlot *resultSlot;
47
0
  PlanState  *outerPlan;
48
0
  ExprContext *econtext;
49
50
0
  CHECK_FOR_INTERRUPTS();
51
52
0
  econtext = node->ps.ps_ExprContext;
53
54
  /*
55
   * Reset per-tuple context to free expression-evaluation storage allocated
56
   * for a potentially previously returned tuple. Note that the SRF argument
57
   * context has a different lifetime and is reset below.
58
   */
59
0
  ResetExprContext(econtext);
60
61
  /*
62
   * Check to see if we're still projecting out tuples from a previous scan
63
   * tuple (because there is a function-returning-set in the projection
64
   * expressions).  If so, try to project another one.
65
   */
66
0
  if (node->pending_srf_tuples)
67
0
  {
68
0
    resultSlot = ExecProjectSRF(node, true);
69
70
0
    if (resultSlot != NULL)
71
0
      return resultSlot;
72
0
  }
73
74
  /*
75
   * Get another input tuple and project SRFs from it.
76
   */
77
0
  for (;;)
78
0
  {
79
    /*
80
     * Reset argument context to free any expression evaluation storage
81
     * allocated in the previous tuple cycle.  Note this can't happen
82
     * until we're done projecting out tuples from a scan tuple, as
83
     * ValuePerCall functions are allowed to reference the arguments for
84
     * each returned tuple.  However, if we loop around after finding that
85
     * no rows are produced from a scan tuple, we should reset, to avoid
86
     * leaking memory when many successive scan tuples produce no rows.
87
     */
88
0
    MemoryContextReset(node->argcontext);
89
90
    /*
91
     * Retrieve tuples from the outer plan until there are no more.
92
     */
93
0
    outerPlan = outerPlanState(node);
94
0
    outerTupleSlot = ExecProcNode(outerPlan);
95
96
0
    if (TupIsNull(outerTupleSlot))
97
0
      return NULL;
98
99
    /*
100
     * Prepare to compute projection expressions, which will expect to
101
     * access the input tuples as varno OUTER.
102
     */
103
0
    econtext->ecxt_outertuple = outerTupleSlot;
104
105
    /* Evaluate the expressions */
106
0
    resultSlot = ExecProjectSRF(node, false);
107
108
    /*
109
     * Return the tuple unless the projection produced no rows (due to an
110
     * empty set), in which case we must loop back to see if there are
111
     * more outerPlan tuples.
112
     */
113
0
    if (resultSlot)
114
0
      return resultSlot;
115
116
    /*
117
     * When we do loop back, we'd better reset the econtext again, just in
118
     * case the SRF leaked some memory there.
119
     */
120
0
    ResetExprContext(econtext);
121
0
  }
122
123
0
  return NULL;
124
0
}
125
126
/* ----------------------------------------------------------------
127
 *    ExecProjectSRF
128
 *
129
 *    Project a targetlist containing one or more set-returning functions.
130
 *
131
 *    'continuing' indicates whether to continue projecting rows for the
132
 *    same input tuple; or whether a new input tuple is being projected.
133
 *
134
 *    Returns NULL if no output tuple has been produced.
135
 *
136
 * ----------------------------------------------------------------
137
 */
138
static TupleTableSlot *
139
ExecProjectSRF(ProjectSetState *node, bool continuing)
140
0
{
141
0
  TupleTableSlot *resultSlot = node->ps.ps_ResultTupleSlot;
142
0
  ExprContext *econtext = node->ps.ps_ExprContext;
143
0
  MemoryContext oldcontext;
144
0
  bool    hassrf PG_USED_FOR_ASSERTS_ONLY;
145
0
  bool    hasresult;
146
0
  int     argno;
147
148
0
  ExecClearTuple(resultSlot);
149
150
  /* Call SRFs, as well as plain expressions, in per-tuple context */
151
0
  oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
152
153
  /*
154
   * Assume no further tuples are produced unless an ExprMultipleResult is
155
   * encountered from a set returning function.
156
   */
157
0
  node->pending_srf_tuples = false;
158
159
0
  hassrf = hasresult = false;
160
0
  for (argno = 0; argno < node->nelems; argno++)
161
0
  {
162
0
    Node     *elem = node->elems[argno];
163
0
    ExprDoneCond *isdone = &node->elemdone[argno];
164
0
    Datum    *result = &resultSlot->tts_values[argno];
165
0
    bool     *isnull = &resultSlot->tts_isnull[argno];
166
167
0
    if (continuing && *isdone == ExprEndResult)
168
0
    {
169
      /*
170
       * If we're continuing to project output rows from a source tuple,
171
       * return NULLs once the SRF has been exhausted.
172
       */
173
0
      *result = (Datum) 0;
174
0
      *isnull = true;
175
0
      hassrf = true;
176
0
    }
177
0
    else if (IsA(elem, SetExprState))
178
0
    {
179
      /*
180
       * Evaluate SRF - possibly continuing previously started output.
181
       */
182
0
      *result = ExecMakeFunctionResultSet((SetExprState *) elem,
183
0
                        econtext, node->argcontext,
184
0
                        isnull, isdone);
185
186
0
      if (*isdone != ExprEndResult)
187
0
        hasresult = true;
188
0
      if (*isdone == ExprMultipleResult)
189
0
        node->pending_srf_tuples = true;
190
0
      hassrf = true;
191
0
    }
192
0
    else
193
0
    {
194
      /* Non-SRF tlist expression, just evaluate normally. */
195
0
      *result = ExecEvalExpr((ExprState *) elem, econtext, isnull);
196
0
      *isdone = ExprSingleResult;
197
0
    }
198
0
  }
199
200
0
  MemoryContextSwitchTo(oldcontext);
201
202
  /* ProjectSet should not be used if there's no SRFs */
203
0
  Assert(hassrf);
204
205
  /*
206
   * If all the SRFs returned ExprEndResult, we consider that as no row
207
   * being produced.
208
   */
209
0
  if (hasresult)
210
0
  {
211
0
    ExecStoreVirtualTuple(resultSlot);
212
0
    return resultSlot;
213
0
  }
214
215
0
  return NULL;
216
0
}
217
218
/* ----------------------------------------------------------------
219
 *    ExecInitProjectSet
220
 *
221
 *    Creates the run-time state information for the ProjectSet node
222
 *    produced by the planner and initializes outer relations
223
 *    (child nodes).
224
 * ----------------------------------------------------------------
225
 */
226
ProjectSetState *
227
ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags)
228
0
{
229
0
  ProjectSetState *state;
230
0
  ListCell   *lc;
231
0
  int     off;
232
233
  /* check for unsupported flags */
234
0
  Assert(!(eflags & (EXEC_FLAG_MARK | EXEC_FLAG_BACKWARD)));
235
236
  /*
237
   * create state structure
238
   */
239
0
  state = makeNode(ProjectSetState);
240
0
  state->ps.plan = (Plan *) node;
241
0
  state->ps.state = estate;
242
0
  state->ps.ExecProcNode = ExecProjectSet;
243
244
0
  state->pending_srf_tuples = false;
245
246
  /*
247
   * Miscellaneous initialization
248
   *
249
   * create expression context for node
250
   */
251
0
  ExecAssignExprContext(estate, &state->ps);
252
253
  /*
254
   * initialize child nodes
255
   */
256
0
  outerPlanState(state) = ExecInitNode(outerPlan(node), estate, eflags);
257
258
  /*
259
   * we don't use inner plan
260
   */
261
0
  Assert(innerPlan(node) == NULL);
262
263
  /*
264
   * tuple table and result type initialization
265
   */
266
0
  ExecInitResultTupleSlotTL(&state->ps, &TTSOpsVirtual);
267
268
  /* Create workspace for per-tlist-entry expr state & SRF-is-done state */
269
0
  state->nelems = list_length(node->plan.targetlist);
270
0
  state->elems = (Node **)
271
0
    palloc(sizeof(Node *) * state->nelems);
272
0
  state->elemdone = (ExprDoneCond *)
273
0
    palloc(sizeof(ExprDoneCond) * state->nelems);
274
275
  /*
276
   * Build expressions to evaluate targetlist.  We can't use
277
   * ExecBuildProjectionInfo here, since that doesn't deal with SRFs.
278
   * Instead compile each expression separately, using
279
   * ExecInitFunctionResultSet where applicable.
280
   */
281
0
  off = 0;
282
0
  foreach(lc, node->plan.targetlist)
283
0
  {
284
0
    TargetEntry *te = (TargetEntry *) lfirst(lc);
285
0
    Expr     *expr = te->expr;
286
287
0
    if ((IsA(expr, FuncExpr) && ((FuncExpr *) expr)->funcretset) ||
288
0
      (IsA(expr, OpExpr) && ((OpExpr *) expr)->opretset))
289
0
    {
290
0
      state->elems[off] = (Node *)
291
0
        ExecInitFunctionResultSet(expr, state->ps.ps_ExprContext,
292
0
                      &state->ps);
293
0
    }
294
0
    else
295
0
    {
296
0
      Assert(!expression_returns_set((Node *) expr));
297
0
      state->elems[off] = (Node *) ExecInitExpr(expr, &state->ps);
298
0
    }
299
300
0
    off++;
301
0
  }
302
303
  /* We don't support any qual on ProjectSet nodes */
304
0
  Assert(node->plan.qual == NIL);
305
306
  /*
307
   * Create a memory context that ExecMakeFunctionResultSet can use to
308
   * evaluate function arguments in.  We can't use the per-tuple context for
309
   * this because it gets reset too often; but we don't want to leak
310
   * evaluation results into the query-lifespan context either.  We use one
311
   * context for the arguments of all tSRFs, as they have roughly equivalent
312
   * lifetimes.
313
   */
314
0
  state->argcontext = AllocSetContextCreate(CurrentMemoryContext,
315
0
                        "tSRF function arguments",
316
0
                        ALLOCSET_DEFAULT_SIZES);
317
318
0
  return state;
319
0
}
320
321
/* ----------------------------------------------------------------
322
 *    ExecEndProjectSet
323
 *
324
 *    frees up storage allocated through C routines
325
 * ----------------------------------------------------------------
326
 */
327
void
328
ExecEndProjectSet(ProjectSetState *node)
329
0
{
330
  /*
331
   * shut down subplans
332
   */
333
0
  ExecEndNode(outerPlanState(node));
334
0
}
335
336
void
337
ExecReScanProjectSet(ProjectSetState *node)
338
0
{
339
0
  PlanState  *outerPlan = outerPlanState(node);
340
341
  /* Forget any incompletely-evaluated SRFs */
342
0
  node->pending_srf_tuples = false;
343
344
  /*
345
   * If chgParam of subnode is not null then plan will be re-scanned by
346
   * first ExecProcNode.
347
   */
348
0
  if (outerPlan->chgParam == NULL)
349
0
    ExecReScan(outerPlan);
350
0
}