Coverage Report

Created: 2025-06-15 06:31

/src/postgres/src/backend/executor/execCurrent.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * execCurrent.c
4
 *    executor support for WHERE CURRENT OF cursor
5
 *
6
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
9
 *  src/backend/executor/execCurrent.c
10
 *
11
 *-------------------------------------------------------------------------
12
 */
13
#include "postgres.h"
14
15
#include "access/genam.h"
16
#include "access/relscan.h"
17
#include "access/sysattr.h"
18
#include "catalog/pg_type.h"
19
#include "executor/executor.h"
20
#include "utils/builtins.h"
21
#include "utils/lsyscache.h"
22
#include "utils/portal.h"
23
#include "utils/rel.h"
24
25
26
static char *fetch_cursor_param_value(ExprContext *econtext, int paramId);
27
static ScanState *search_plan_tree(PlanState *node, Oid table_oid,
28
                   bool *pending_rescan);
29
30
31
/*
32
 * execCurrentOf
33
 *
34
 * Given a CURRENT OF expression and the OID of a table, determine which row
35
 * of the table is currently being scanned by the cursor named by CURRENT OF,
36
 * and return the row's TID into *current_tid.
37
 *
38
 * Returns true if a row was identified.  Returns false if the cursor is valid
39
 * for the table but is not currently scanning a row of the table (this is a
40
 * legal situation in inheritance cases).  Raises error if cursor is not a
41
 * valid updatable scan of the specified table.
42
 */
43
bool
44
execCurrentOf(CurrentOfExpr *cexpr,
45
        ExprContext *econtext,
46
        Oid table_oid,
47
        ItemPointer current_tid)
48
0
{
49
0
  char     *cursor_name;
50
0
  char     *table_name;
51
0
  Portal    portal;
52
0
  QueryDesc  *queryDesc;
53
54
  /* Get the cursor name --- may have to look up a parameter reference */
55
0
  if (cexpr->cursor_name)
56
0
    cursor_name = cexpr->cursor_name;
57
0
  else
58
0
    cursor_name = fetch_cursor_param_value(econtext, cexpr->cursor_param);
59
60
  /* Fetch table name for possible use in error messages */
61
0
  table_name = get_rel_name(table_oid);
62
0
  if (table_name == NULL)
63
0
    elog(ERROR, "cache lookup failed for relation %u", table_oid);
64
65
  /* Find the cursor's portal */
66
0
  portal = GetPortalByName(cursor_name);
67
0
  if (!PortalIsValid(portal))
68
0
    ereport(ERROR,
69
0
        (errcode(ERRCODE_UNDEFINED_CURSOR),
70
0
         errmsg("cursor \"%s\" does not exist", cursor_name)));
71
72
  /*
73
   * We have to watch out for non-SELECT queries as well as held cursors,
74
   * both of which may have null queryDesc.
75
   */
76
0
  if (portal->strategy != PORTAL_ONE_SELECT)
77
0
    ereport(ERROR,
78
0
        (errcode(ERRCODE_INVALID_CURSOR_STATE),
79
0
         errmsg("cursor \"%s\" is not a SELECT query",
80
0
            cursor_name)));
81
0
  queryDesc = portal->queryDesc;
82
0
  if (queryDesc == NULL || queryDesc->estate == NULL)
83
0
    ereport(ERROR,
84
0
        (errcode(ERRCODE_INVALID_CURSOR_STATE),
85
0
         errmsg("cursor \"%s\" is held from a previous transaction",
86
0
            cursor_name)));
87
88
  /*
89
   * We have two different strategies depending on whether the cursor uses
90
   * FOR UPDATE/SHARE or not.  The reason for supporting both is that the
91
   * FOR UPDATE code is able to identify a target table in many cases where
92
   * the other code can't, while the non-FOR-UPDATE case allows use of WHERE
93
   * CURRENT OF with an insensitive cursor.
94
   */
95
0
  if (queryDesc->estate->es_rowmarks)
96
0
  {
97
0
    ExecRowMark *erm;
98
0
    Index   i;
99
100
    /*
101
     * Here, the query must have exactly one FOR UPDATE/SHARE reference to
102
     * the target table, and we dig the ctid info out of that.
103
     */
104
0
    erm = NULL;
105
0
    for (i = 0; i < queryDesc->estate->es_range_table_size; i++)
106
0
    {
107
0
      ExecRowMark *thiserm = queryDesc->estate->es_rowmarks[i];
108
109
0
      if (thiserm == NULL ||
110
0
        !RowMarkRequiresRowShareLock(thiserm->markType))
111
0
        continue;   /* ignore non-FOR UPDATE/SHARE items */
112
113
0
      if (thiserm->relid == table_oid)
114
0
      {
115
0
        if (erm)
116
0
          ereport(ERROR,
117
0
              (errcode(ERRCODE_INVALID_CURSOR_STATE),
118
0
               errmsg("cursor \"%s\" has multiple FOR UPDATE/SHARE references to table \"%s\"",
119
0
                  cursor_name, table_name)));
120
0
        erm = thiserm;
121
0
      }
122
0
    }
123
124
0
    if (erm == NULL)
125
0
      ereport(ERROR,
126
0
          (errcode(ERRCODE_INVALID_CURSOR_STATE),
127
0
           errmsg("cursor \"%s\" does not have a FOR UPDATE/SHARE reference to table \"%s\"",
128
0
              cursor_name, table_name)));
129
130
    /*
131
     * The cursor must have a current result row: per the SQL spec, it's
132
     * an error if not.
133
     */
134
0
    if (portal->atStart || portal->atEnd)
135
0
      ereport(ERROR,
136
0
          (errcode(ERRCODE_INVALID_CURSOR_STATE),
137
0
           errmsg("cursor \"%s\" is not positioned on a row",
138
0
              cursor_name)));
139
140
    /* Return the currently scanned TID, if there is one */
141
0
    if (ItemPointerIsValid(&(erm->curCtid)))
142
0
    {
143
0
      *current_tid = erm->curCtid;
144
0
      return true;
145
0
    }
146
147
    /*
148
     * This table didn't produce the cursor's current row; some other
149
     * inheritance child of the same parent must have.  Signal caller to
150
     * do nothing on this table.
151
     */
152
0
    return false;
153
0
  }
154
0
  else
155
0
  {
156
    /*
157
     * Without FOR UPDATE, we dig through the cursor's plan to find the
158
     * scan node.  Fail if it's not there or buried underneath
159
     * aggregation.
160
     */
161
0
    ScanState  *scanstate;
162
0
    bool    pending_rescan = false;
163
164
0
    scanstate = search_plan_tree(queryDesc->planstate, table_oid,
165
0
                   &pending_rescan);
166
0
    if (!scanstate)
167
0
      ereport(ERROR,
168
0
          (errcode(ERRCODE_INVALID_CURSOR_STATE),
169
0
           errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
170
0
              cursor_name, table_name)));
171
172
    /*
173
     * The cursor must have a current result row: per the SQL spec, it's
174
     * an error if not.  We test this at the top level, rather than at the
175
     * scan node level, because in inheritance cases any one table scan
176
     * could easily not be on a row. We want to return false, not raise
177
     * error, if the passed-in table OID is for one of the inactive scans.
178
     */
179
0
    if (portal->atStart || portal->atEnd)
180
0
      ereport(ERROR,
181
0
          (errcode(ERRCODE_INVALID_CURSOR_STATE),
182
0
           errmsg("cursor \"%s\" is not positioned on a row",
183
0
              cursor_name)));
184
185
    /*
186
     * Now OK to return false if we found an inactive scan.  It is
187
     * inactive either if it's not positioned on a row, or there's a
188
     * rescan pending for it.
189
     */
190
0
    if (TupIsNull(scanstate->ss_ScanTupleSlot) || pending_rescan)
191
0
      return false;
192
193
    /*
194
     * Extract TID of the scan's current row.  The mechanism for this is
195
     * in principle scan-type-dependent, but for most scan types, we can
196
     * just dig the TID out of the physical scan tuple.
197
     */
198
0
    if (IsA(scanstate, IndexOnlyScanState))
199
0
    {
200
      /*
201
       * For IndexOnlyScan, the tuple stored in ss_ScanTupleSlot may be
202
       * a virtual tuple that does not have the ctid column, so we have
203
       * to get the TID from xs_heaptid.
204
       */
205
0
      IndexScanDesc scan = ((IndexOnlyScanState *) scanstate)->ioss_ScanDesc;
206
207
0
      *current_tid = scan->xs_heaptid;
208
0
    }
209
0
    else
210
0
    {
211
      /*
212
       * Default case: try to fetch TID from the scan node's current
213
       * tuple.  As an extra cross-check, verify tableoid in the current
214
       * tuple.  If the scan hasn't provided a physical tuple, we have
215
       * to fail.
216
       */
217
0
      Datum   ldatum;
218
0
      bool    lisnull;
219
0
      ItemPointer tuple_tid;
220
221
#ifdef USE_ASSERT_CHECKING
222
      ldatum = slot_getsysattr(scanstate->ss_ScanTupleSlot,
223
                   TableOidAttributeNumber,
224
                   &lisnull);
225
      if (lisnull)
226
        ereport(ERROR,
227
            (errcode(ERRCODE_INVALID_CURSOR_STATE),
228
             errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
229
                cursor_name, table_name)));
230
      Assert(DatumGetObjectId(ldatum) == table_oid);
231
#endif
232
233
0
      ldatum = slot_getsysattr(scanstate->ss_ScanTupleSlot,
234
0
                   SelfItemPointerAttributeNumber,
235
0
                   &lisnull);
236
0
      if (lisnull)
237
0
        ereport(ERROR,
238
0
            (errcode(ERRCODE_INVALID_CURSOR_STATE),
239
0
             errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
240
0
                cursor_name, table_name)));
241
0
      tuple_tid = (ItemPointer) DatumGetPointer(ldatum);
242
243
0
      *current_tid = *tuple_tid;
244
0
    }
245
246
0
    Assert(ItemPointerIsValid(current_tid));
247
248
0
    return true;
249
0
  }
250
0
}
251
252
/*
253
 * fetch_cursor_param_value
254
 *
255
 * Fetch the string value of a param, verifying it is of type REFCURSOR.
256
 */
257
static char *
258
fetch_cursor_param_value(ExprContext *econtext, int paramId)
259
0
{
260
0
  ParamListInfo paramInfo = econtext->ecxt_param_list_info;
261
262
0
  if (paramInfo &&
263
0
    paramId > 0 && paramId <= paramInfo->numParams)
264
0
  {
265
0
    ParamExternData *prm;
266
0
    ParamExternData prmdata;
267
268
    /* give hook a chance in case parameter is dynamic */
269
0
    if (paramInfo->paramFetch != NULL)
270
0
      prm = paramInfo->paramFetch(paramInfo, paramId, false, &prmdata);
271
0
    else
272
0
      prm = &paramInfo->params[paramId - 1];
273
274
0
    if (OidIsValid(prm->ptype) && !prm->isnull)
275
0
    {
276
      /* safety check in case hook did something unexpected */
277
0
      if (prm->ptype != REFCURSOROID)
278
0
        ereport(ERROR,
279
0
            (errcode(ERRCODE_DATATYPE_MISMATCH),
280
0
             errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
281
0
                paramId,
282
0
                format_type_be(prm->ptype),
283
0
                format_type_be(REFCURSOROID))));
284
285
      /* We know that refcursor uses text's I/O routines */
286
0
      return TextDatumGetCString(prm->value);
287
0
    }
288
0
  }
289
290
0
  ereport(ERROR,
291
0
      (errcode(ERRCODE_UNDEFINED_OBJECT),
292
0
       errmsg("no value found for parameter %d", paramId)));
293
0
  return NULL;
294
0
}
295
296
/*
297
 * search_plan_tree
298
 *
299
 * Search through a PlanState tree for a scan node on the specified table.
300
 * Return NULL if not found or multiple candidates.
301
 *
302
 * CAUTION: this function is not charged simply with finding some candidate
303
 * scan, but with ensuring that that scan returned the plan tree's current
304
 * output row.  That's why we must reject multiple-match cases.
305
 *
306
 * If a candidate is found, set *pending_rescan to true if that candidate
307
 * or any node above it has a pending rescan action, i.e. chgParam != NULL.
308
 * That indicates that we shouldn't consider the node to be positioned on a
309
 * valid tuple, even if its own state would indicate that it is.  (Caller
310
 * must initialize *pending_rescan to false, and should not trust its state
311
 * if multiple candidates are found.)
312
 */
313
static ScanState *
314
search_plan_tree(PlanState *node, Oid table_oid,
315
         bool *pending_rescan)
316
0
{
317
0
  ScanState  *result = NULL;
318
319
0
  if (node == NULL)
320
0
    return NULL;
321
0
  switch (nodeTag(node))
322
0
  {
323
      /*
324
       * Relation scan nodes can all be treated alike: check to see if
325
       * they are scanning the specified table.
326
       *
327
       * ForeignScan and CustomScan might not have a currentRelation, in
328
       * which case we just ignore them.  (We dare not descend to any
329
       * child plan nodes they might have, since we do not know the
330
       * relationship of such a node's current output tuple to the
331
       * children's current outputs.)
332
       */
333
0
    case T_SeqScanState:
334
0
    case T_SampleScanState:
335
0
    case T_IndexScanState:
336
0
    case T_IndexOnlyScanState:
337
0
    case T_BitmapHeapScanState:
338
0
    case T_TidScanState:
339
0
    case T_TidRangeScanState:
340
0
    case T_ForeignScanState:
341
0
    case T_CustomScanState:
342
0
      {
343
0
        ScanState  *sstate = (ScanState *) node;
344
345
0
        if (sstate->ss_currentRelation &&
346
0
          RelationGetRelid(sstate->ss_currentRelation) == table_oid)
347
0
          result = sstate;
348
0
        break;
349
0
      }
350
351
      /*
352
       * For Append, we can check each input node.  It is safe to
353
       * descend to the inputs because only the input that resulted in
354
       * the Append's current output node could be positioned on a tuple
355
       * at all; the other inputs are either at EOF or not yet started.
356
       * Hence, if the desired table is scanned by some
357
       * currently-inactive input node, we will find that node but then
358
       * our caller will realize that it didn't emit the tuple of
359
       * interest.
360
       *
361
       * We do need to watch out for multiple matches (possible if
362
       * Append was from UNION ALL rather than an inheritance tree).
363
       *
364
       * Note: we can NOT descend through MergeAppend similarly, since
365
       * its inputs are likely all active, and we don't know which one
366
       * returned the current output tuple.  (Perhaps that could be
367
       * fixed if we were to let this code know more about MergeAppend's
368
       * internal state, but it does not seem worth the trouble.  Users
369
       * should not expect plans for ORDER BY queries to be considered
370
       * simply-updatable, since they won't be if the sorting is
371
       * implemented by a Sort node.)
372
       */
373
0
    case T_AppendState:
374
0
      {
375
0
        AppendState *astate = (AppendState *) node;
376
0
        int     i;
377
378
0
        for (i = 0; i < astate->as_nplans; i++)
379
0
        {
380
0
          ScanState  *elem = search_plan_tree(astate->appendplans[i],
381
0
                            table_oid,
382
0
                            pending_rescan);
383
384
0
          if (!elem)
385
0
            continue;
386
0
          if (result)
387
0
            return NULL; /* multiple matches */
388
0
          result = elem;
389
0
        }
390
0
        break;
391
0
      }
392
393
      /*
394
       * Result and Limit can be descended through (these are safe
395
       * because they always return their input's current row)
396
       */
397
0
    case T_ResultState:
398
0
    case T_LimitState:
399
0
      result = search_plan_tree(outerPlanState(node),
400
0
                    table_oid,
401
0
                    pending_rescan);
402
0
      break;
403
404
      /*
405
       * SubqueryScan too, but it keeps the child in a different place
406
       */
407
0
    case T_SubqueryScanState:
408
0
      result = search_plan_tree(((SubqueryScanState *) node)->subplan,
409
0
                    table_oid,
410
0
                    pending_rescan);
411
0
      break;
412
413
0
    default:
414
      /* Otherwise, assume we can't descend through it */
415
0
      break;
416
0
  }
417
418
  /*
419
   * If we found a candidate at or below this node, then this node's
420
   * chgParam indicates a pending rescan that will affect the candidate.
421
   */
422
0
  if (result && node->chgParam != NULL)
423
0
    *pending_rescan = true;
424
425
0
  return result;
426
0
}