/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 = ¶mInfo->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 | } |