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