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-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
5995 bruce@momjian.us 44 :CBC 193 : 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 */
6152 tgl@sss.pgh.pa.us 55 [ + + ]: 193 : if (cexpr->cursor_name)
56 : 133 : cursor_name = cexpr->cursor_name;
57 : : else
5270 58 : 60 : cursor_name = fetch_cursor_param_value(econtext, cexpr->cursor_param);
59 : :
60 : : /* Fetch table name for possible use in error messages */
6152 61 : 193 : table_name = get_rel_name(table_oid);
62 [ - + ]: 193 : if (table_name == NULL)
6152 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 */
6152 tgl@sss.pgh.pa.us 66 :CBC 193 : portal = GetPortalByName(cursor_name);
67 [ + + ]: 193 : if (!PortalIsValid(portal))
68 [ + - ]: 3 : 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 [ - + ]: 190 : if (portal->strategy != PORTAL_ONE_SELECT)
6152 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)));
2311 peter_e@gmx.net 81 :CBC 190 : queryDesc = portal->queryDesc;
5628 tgl@sss.pgh.pa.us 82 [ + + - + ]: 190 : if (queryDesc == NULL || queryDesc->estate == NULL)
6152 83 [ + - ]: 3 : 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 : : */
2015 95 [ + + ]: 187 : 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 : : */
5628 104 : 48 : erm = NULL;
2015 105 [ + + ]: 171 : for (i = 0; i < queryDesc->estate->es_range_table_size; i++)
106 : : {
107 : 126 : ExecRowMark *thiserm = queryDesc->estate->es_rowmarks[i];
108 : :
109 [ + + ]: 126 : if (thiserm == NULL ||
110 [ + + ]: 90 : !RowMarkRequiresRowShareLock(thiserm->markType))
5284 111 : 48 : continue; /* ignore non-FOR UPDATE/SHARE items */
112 : :
3311 113 [ + + ]: 78 : if (thiserm->relid == table_oid)
114 : : {
5628 115 [ + + ]: 48 : if (erm)
116 [ + - ]: 3 : 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 : 45 : erm = thiserm;
121 : : }
122 : : }
123 : :
124 [ + + ]: 45 : if (erm == NULL)
125 [ + - ]: 3 : 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 [ + - - + ]: 42 : if (portal->atStart || portal->atEnd)
5628 tgl@sss.pgh.pa.us 135 [ # # ]:UBC 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 */
5628 tgl@sss.pgh.pa.us 141 [ + + ]:CBC 42 : if (ItemPointerIsValid(&(erm->curCtid)))
142 : : {
143 : 30 : *current_tid = erm->curCtid;
144 : 30 : 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 : : */
6152 152 : 12 : 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;
2030 162 : 139 : bool pending_rescan = false;
163 : :
164 : 139 : scanstate = search_plan_tree(queryDesc->planstate, table_oid,
165 : : &pending_rescan);
5628 166 [ + + ]: 139 : if (!scanstate)
167 [ + - ]: 12 : 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 [ + + + + ]: 127 : if (portal->atStart || portal->atEnd)
180 [ + - ]: 6 : 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 : : */
2030 190 [ + - + + : 121 : if (TupIsNull(scanstate->ss_ScanTupleSlot) || pending_rescan)
- + ]
5628 191 : 13 : 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 : : */
2220 198 [ + + ]: 108 : 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_ctup.t_self.
204 : : */
205 : 3 : IndexScanDesc scan = ((IndexOnlyScanState *) scanstate)->ioss_ScanDesc;
206 : :
1861 andres@anarazel.de 207 : 3 : *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
1976 222 : 105 : ldatum = slot_getsysattr(scanstate->ss_ScanTupleSlot,
223 : : TableOidAttributeNumber,
224 : : &lisnull);
225 [ - + ]: 105 : if (lisnull)
2220 tgl@sss.pgh.pa.us 226 [ # # ]:UBC 0 : 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)));
2220 tgl@sss.pgh.pa.us 230 [ - + ]:CBC 105 : Assert(DatumGetObjectId(ldatum) == table_oid);
231 : : #endif
232 : :
1976 andres@anarazel.de 233 : 105 : ldatum = slot_getsysattr(scanstate->ss_ScanTupleSlot,
234 : : SelfItemPointerAttributeNumber,
235 : : &lisnull);
236 [ - + ]: 105 : if (lisnull)
2220 tgl@sss.pgh.pa.us 237 [ # # ]:UBC 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)));
2220 tgl@sss.pgh.pa.us 241 :CBC 105 : tuple_tid = (ItemPointer) DatumGetPointer(ldatum);
242 : :
243 : 105 : *current_tid = *tuple_tid;
244 : : }
245 : :
246 [ - + ]: 108 : Assert(ItemPointerIsValid(current_tid));
247 : :
5628 248 : 108 : 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 *
5270 258 : 60 : fetch_cursor_param_value(ExprContext *econtext, int paramId)
259 : : {
6152 260 : 60 : ParamListInfo paramInfo = econtext->ecxt_param_list_info;
261 : :
262 [ + - + - ]: 60 : if (paramInfo &&
263 [ + - ]: 60 : paramId > 0 && paramId <= paramInfo->numParams)
264 : : {
265 : : ParamExternData *prm;
266 : : ParamExternData prmdata;
267 : :
268 : : /* give hook a chance in case parameter is dynamic */
2306 269 [ + - ]: 60 : if (paramInfo->paramFetch != NULL)
270 : 60 : prm = paramInfo->paramFetch(paramInfo, paramId, false, &prmdata);
271 : : else
2306 tgl@sss.pgh.pa.us 272 :UBC 0 : prm = ¶mInfo->params[paramId - 1];
273 : :
6152 tgl@sss.pgh.pa.us 274 [ + - + - ]:CBC 60 : if (OidIsValid(prm->ptype) && !prm->isnull)
275 : : {
276 : : /* safety check in case hook did something unexpected */
5275 277 [ - + ]: 60 : if (prm->ptype != REFCURSOROID)
5275 tgl@sss.pgh.pa.us 278 [ # # ]:UBC 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 */
5864 tgl@sss.pgh.pa.us 286 :CBC 60 : return TextDatumGetCString(prm->value);
287 : : }
288 : : }
289 : :
6152 tgl@sss.pgh.pa.us 290 [ # # ]:UBC 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 *
2030 tgl@sss.pgh.pa.us 314 :CBC 201 : search_plan_tree(PlanState *node, Oid table_oid,
315 : : bool *pending_rescan)
316 : : {
317 : 201 : ScanState *result = NULL;
318 : :
6152 319 [ - + ]: 201 : if (node == NULL)
6152 tgl@sss.pgh.pa.us 320 :UBC 0 : return NULL;
6152 tgl@sss.pgh.pa.us 321 [ + + - - :CBC 201 : 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 : 170 : 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 : : {
5995 bruce@momjian.us 343 : 170 : ScanState *sstate = (ScanState *) node;
344 : :
1182 tgl@sss.pgh.pa.us 345 [ + - ]: 170 : if (sstate->ss_currentRelation &&
346 [ + + ]: 170 : RelationGetRelid(sstate->ss_currentRelation) == table_oid)
2030 347 : 127 : result = sstate;
5995 bruce@momjian.us 348 : 170 : 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 : : */
6152 tgl@sss.pgh.pa.us 373 : 22 : case T_AppendState:
374 : : {
5995 bruce@momjian.us 375 : 22 : AppendState *astate = (AppendState *) node;
376 : : int i;
377 : :
378 [ + + ]: 84 : for (i = 0; i < astate->as_nplans; i++)
379 : : {
380 : 62 : ScanState *elem = search_plan_tree(astate->appendplans[i],
381 : : table_oid,
382 : : pending_rescan);
383 : :
4931 tgl@sss.pgh.pa.us 384 [ + + ]: 62 : if (!elem)
385 : 40 : continue;
386 [ - + ]: 22 : if (result)
4931 tgl@sss.pgh.pa.us 387 :UBC 0 : return NULL; /* multiple matches */
4931 tgl@sss.pgh.pa.us 388 :CBC 22 : result = elem;
389 : : }
2030 390 : 22 : 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 : : */
6152 tgl@sss.pgh.pa.us 397 :UBC 0 : case T_ResultState:
398 : : case T_LimitState:
647 399 : 0 : result = search_plan_tree(outerPlanState(node),
400 : : table_oid,
401 : : pending_rescan);
2030 402 : 0 : break;
403 : :
404 : : /*
405 : : * SubqueryScan too, but it keeps the child in a different place
406 : : */
6152 407 : 0 : case T_SubqueryScanState:
2030 408 : 0 : result = search_plan_tree(((SubqueryScanState *) node)->subplan,
409 : : table_oid,
410 : : pending_rescan);
411 : 0 : break;
412 : :
6152 tgl@sss.pgh.pa.us 413 :CBC 9 : default:
414 : : /* Otherwise, assume we can't descend through it */
415 : 9 : 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 : : */
2030 422 [ + + - + ]: 201 : if (result && node->chgParam != NULL)
2030 tgl@sss.pgh.pa.us 423 :UBC 0 : *pending_rescan = true;
424 : :
2030 tgl@sss.pgh.pa.us 425 :CBC 201 : return result;
426 : : }
|