Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * execCurrent.c
4 : * executor support for WHERE CURRENT OF cursor
5 : *
6 : * Portions Copyright (c) 1996-2024, 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 392 : 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 */
55 392 : if (cexpr->cursor_name)
56 272 : cursor_name = cexpr->cursor_name;
57 : else
58 120 : cursor_name = fetch_cursor_param_value(econtext, cexpr->cursor_param);
59 :
60 : /* Fetch table name for possible use in error messages */
61 392 : table_name = get_rel_name(table_oid);
62 392 : if (table_name == NULL)
63 0 : elog(ERROR, "cache lookup failed for relation %u", table_oid);
64 :
65 : /* Find the cursor's portal */
66 392 : portal = GetPortalByName(cursor_name);
67 392 : if (!PortalIsValid(portal))
68 6 : 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 386 : if (portal->strategy != PORTAL_ONE_SELECT)
77 0 : ereport(ERROR,
78 : (errcode(ERRCODE_INVALID_CURSOR_STATE),
79 : errmsg("cursor \"%s\" is not a SELECT query",
80 : cursor_name)));
81 386 : queryDesc = portal->queryDesc;
82 386 : if (queryDesc == NULL || queryDesc->estate == NULL)
83 6 : 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 : */
95 380 : if (queryDesc->estate->es_rowmarks)
96 : {
97 : ExecRowMark *erm;
98 : 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 96 : erm = NULL;
105 342 : for (i = 0; i < queryDesc->estate->es_range_table_size; i++)
106 : {
107 252 : ExecRowMark *thiserm = queryDesc->estate->es_rowmarks[i];
108 :
109 252 : if (thiserm == NULL ||
110 180 : !RowMarkRequiresRowShareLock(thiserm->markType))
111 96 : continue; /* ignore non-FOR UPDATE/SHARE items */
112 :
113 156 : if (thiserm->relid == table_oid)
114 : {
115 96 : if (erm)
116 6 : ereport(ERROR,
117 : (errcode(ERRCODE_INVALID_CURSOR_STATE),
118 : errmsg("cursor \"%s\" has multiple FOR UPDATE/SHARE references to table \"%s\"",
119 : cursor_name, table_name)));
120 90 : erm = thiserm;
121 : }
122 : }
123 :
124 90 : if (erm == NULL)
125 6 : ereport(ERROR,
126 : (errcode(ERRCODE_INVALID_CURSOR_STATE),
127 : errmsg("cursor \"%s\" does not have a FOR UPDATE/SHARE reference to table \"%s\"",
128 : 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 84 : if (portal->atStart || portal->atEnd)
135 0 : ereport(ERROR,
136 : (errcode(ERRCODE_INVALID_CURSOR_STATE),
137 : errmsg("cursor \"%s\" is not positioned on a row",
138 : cursor_name)));
139 :
140 : /* Return the currently scanned TID, if there is one */
141 84 : if (ItemPointerIsValid(&(erm->curCtid)))
142 : {
143 60 : *current_tid = erm->curCtid;
144 60 : return true;
145 : }
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 24 : return false;
153 : }
154 : else
155 : {
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 : ScanState *scanstate;
162 284 : bool pending_rescan = false;
163 :
164 284 : scanstate = search_plan_tree(queryDesc->planstate, table_oid,
165 : &pending_rescan);
166 284 : if (!scanstate)
167 24 : ereport(ERROR,
168 : (errcode(ERRCODE_INVALID_CURSOR_STATE),
169 : errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
170 : 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 260 : if (portal->atStart || portal->atEnd)
180 12 : ereport(ERROR,
181 : (errcode(ERRCODE_INVALID_CURSOR_STATE),
182 : errmsg("cursor \"%s\" is not positioned on a row",
183 : 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 248 : if (TupIsNull(scanstate->ss_ScanTupleSlot) || pending_rescan)
191 26 : 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 222 : if (IsA(scanstate, IndexOnlyScanState))
199 : {
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 6 : IndexScanDesc scan = ((IndexOnlyScanState *) scanstate)->ioss_ScanDesc;
206 :
207 6 : *current_tid = scan->xs_heaptid;
208 : }
209 : else
210 : {
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 : Datum ldatum;
218 : bool lisnull;
219 : 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 216 : ldatum = slot_getsysattr(scanstate->ss_ScanTupleSlot,
234 : SelfItemPointerAttributeNumber,
235 : &lisnull);
236 216 : if (lisnull)
237 0 : ereport(ERROR,
238 : (errcode(ERRCODE_INVALID_CURSOR_STATE),
239 : errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
240 : cursor_name, table_name)));
241 216 : tuple_tid = (ItemPointer) DatumGetPointer(ldatum);
242 :
243 216 : *current_tid = *tuple_tid;
244 : }
245 :
246 : Assert(ItemPointerIsValid(current_tid));
247 :
248 222 : return true;
249 : }
250 : }
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 120 : fetch_cursor_param_value(ExprContext *econtext, int paramId)
259 : {
260 120 : ParamListInfo paramInfo = econtext->ecxt_param_list_info;
261 :
262 120 : if (paramInfo &&
263 120 : paramId > 0 && paramId <= paramInfo->numParams)
264 : {
265 : ParamExternData *prm;
266 : ParamExternData prmdata;
267 :
268 : /* give hook a chance in case parameter is dynamic */
269 120 : if (paramInfo->paramFetch != NULL)
270 120 : prm = paramInfo->paramFetch(paramInfo, paramId, false, &prmdata);
271 : else
272 0 : prm = ¶mInfo->params[paramId - 1];
273 :
274 120 : if (OidIsValid(prm->ptype) && !prm->isnull)
275 : {
276 : /* safety check in case hook did something unexpected */
277 120 : if (prm->ptype != REFCURSOROID)
278 0 : ereport(ERROR,
279 : (errcode(ERRCODE_DATATYPE_MISMATCH),
280 : errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
281 : paramId,
282 : format_type_be(prm->ptype),
283 : format_type_be(REFCURSOROID))));
284 :
285 : /* We know that refcursor uses text's I/O routines */
286 120 : return TextDatumGetCString(prm->value);
287 : }
288 : }
289 :
290 0 : ereport(ERROR,
291 : (errcode(ERRCODE_UNDEFINED_OBJECT),
292 : errmsg("no value found for parameter %d", paramId)));
293 : return NULL;
294 : }
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 408 : search_plan_tree(PlanState *node, Oid table_oid,
315 : bool *pending_rescan)
316 : {
317 408 : ScanState *result = NULL;
318 :
319 408 : if (node == NULL)
320 0 : return NULL;
321 408 : switch (nodeTag(node))
322 : {
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 346 : case T_SeqScanState:
334 : case T_SampleScanState:
335 : case T_IndexScanState:
336 : case T_IndexOnlyScanState:
337 : case T_BitmapHeapScanState:
338 : case T_TidScanState:
339 : case T_TidRangeScanState:
340 : case T_ForeignScanState:
341 : case T_CustomScanState:
342 : {
343 346 : ScanState *sstate = (ScanState *) node;
344 :
345 346 : if (sstate->ss_currentRelation &&
346 346 : RelationGetRelid(sstate->ss_currentRelation) == table_oid)
347 260 : result = sstate;
348 346 : break;
349 : }
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 44 : case T_AppendState:
374 : {
375 44 : AppendState *astate = (AppendState *) node;
376 : int i;
377 :
378 168 : for (i = 0; i < astate->as_nplans; i++)
379 : {
380 124 : ScanState *elem = search_plan_tree(astate->appendplans[i],
381 : table_oid,
382 : pending_rescan);
383 :
384 124 : if (!elem)
385 80 : continue;
386 44 : if (result)
387 0 : return NULL; /* multiple matches */
388 44 : result = elem;
389 : }
390 44 : break;
391 : }
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 : case T_LimitState:
399 0 : result = search_plan_tree(outerPlanState(node),
400 : table_oid,
401 : 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 : table_oid,
410 : pending_rescan);
411 0 : break;
412 :
413 18 : default:
414 : /* Otherwise, assume we can't descend through it */
415 18 : break;
416 : }
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 408 : if (result && node->chgParam != NULL)
423 0 : *pending_rescan = true;
424 :
425 408 : return result;
426 : }
|