1 /*-------------------------------------------------------------------------
4 * executor support for WHERE CURRENT OF cursor
6 * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
9 * $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.4 2007/11/15 22:25:15 momjian Exp $
11 *-------------------------------------------------------------------------
15 #include "catalog/pg_type.h"
16 #include "executor/executor.h"
17 #include "utils/builtins.h"
18 #include "utils/lsyscache.h"
19 #include "utils/portal.h"
22 static char *fetch_param_value(ExprContext *econtext, int paramId);
23 static ScanState *search_plan_tree(PlanState *node, Oid table_oid);
29 * Given a CURRENT OF expression and the OID of a table, determine which row
30 * of the table is currently being scanned by the cursor named by CURRENT OF,
31 * and return the row's TID into *current_tid.
33 * Returns TRUE if a row was identified. Returns FALSE if the cursor is valid
34 * for the table but is not currently scanning a row of the table (this is a
35 * legal situation in inheritance cases). Raises error if cursor is not a
36 * valid updatable scan of the specified table.
39 execCurrentOf(CurrentOfExpr *cexpr,
40 ExprContext *econtext,
42 ItemPointer current_tid)
51 ItemPointer tuple_tid;
53 /* Get the cursor name --- may have to look up a parameter reference */
54 if (cexpr->cursor_name)
55 cursor_name = cexpr->cursor_name;
57 cursor_name = fetch_param_value(econtext, cexpr->cursor_param);
59 /* Fetch table name for possible use in error messages */
60 table_name = get_rel_name(table_oid);
61 if (table_name == NULL)
62 elog(ERROR, "cache lookup failed for relation %u", table_oid);
64 /* Find the cursor's portal */
65 portal = GetPortalByName(cursor_name);
66 if (!PortalIsValid(portal))
68 (errcode(ERRCODE_UNDEFINED_CURSOR),
69 errmsg("cursor \"%s\" does not exist", cursor_name)));
72 * We have to watch out for non-SELECT queries as well as held cursors,
73 * both of which may have null queryDesc.
75 if (portal->strategy != PORTAL_ONE_SELECT)
77 (errcode(ERRCODE_INVALID_CURSOR_STATE),
78 errmsg("cursor \"%s\" is not a SELECT query",
80 queryDesc = PortalGetQueryDesc(portal);
81 if (queryDesc == NULL)
83 (errcode(ERRCODE_INVALID_CURSOR_STATE),
84 errmsg("cursor \"%s\" is held from a previous transaction",
88 * Dig through the cursor's plan to find the scan node. Fail if it's not
89 * there or buried underneath aggregation.
91 scanstate = search_plan_tree(ExecGetActivePlanTree(queryDesc),
95 (errcode(ERRCODE_INVALID_CURSOR_STATE),
96 errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
97 cursor_name, table_name)));
100 * The cursor must have a current result row: per the SQL spec, it's an
101 * error if not. We test this at the top level, rather than at the scan
102 * node level, because in inheritance cases any one table scan could
103 * easily not be on a row. We want to return false, not raise error, if
104 * the passed-in table OID is for one of the inactive scans.
106 if (portal->atStart || portal->atEnd)
108 (errcode(ERRCODE_INVALID_CURSOR_STATE),
109 errmsg("cursor \"%s\" is not positioned on a row",
112 /* Now OK to return false if we found an inactive scan */
113 if (TupIsNull(scanstate->ss_ScanTupleSlot))
116 /* Use slot_getattr to catch any possible mistakes */
117 tuple_tableoid = DatumGetObjectId(slot_getattr(scanstate->ss_ScanTupleSlot,
118 TableOidAttributeNumber,
121 tuple_tid = (ItemPointer)
122 DatumGetPointer(slot_getattr(scanstate->ss_ScanTupleSlot,
123 SelfItemPointerAttributeNumber,
127 Assert(tuple_tableoid == table_oid);
129 *current_tid = *tuple_tid;
137 * Fetch the string value of a param, verifying it is of type REFCURSOR.
140 fetch_param_value(ExprContext *econtext, int paramId)
142 ParamListInfo paramInfo = econtext->ecxt_param_list_info;
145 paramId > 0 && paramId <= paramInfo->numParams)
147 ParamExternData *prm = ¶mInfo->params[paramId - 1];
149 if (OidIsValid(prm->ptype) && !prm->isnull)
151 Assert(prm->ptype == REFCURSOROID);
152 /* We know that refcursor uses text's I/O routines */
153 return DatumGetCString(DirectFunctionCall1(textout,
159 (errcode(ERRCODE_UNDEFINED_OBJECT),
160 errmsg("no value found for parameter %d", paramId)));
167 * Search through a PlanState tree for a scan node on the specified table.
168 * Return NULL if not found or multiple candidates.
171 search_plan_tree(PlanState *node, Oid table_oid)
175 switch (nodeTag(node))
178 * scan nodes can all be treated alike
181 case T_IndexScanState:
182 case T_BitmapHeapScanState:
185 ScanState *sstate = (ScanState *) node;
187 if (RelationGetRelid(sstate->ss_currentRelation) == table_oid)
193 * For Append, we must look through the members; watch out for
194 * multiple matches (possible if it was from UNION ALL)
198 AppendState *astate = (AppendState *) node;
199 ScanState *result = NULL;
202 for (i = 0; i < astate->as_nplans; i++)
204 ScanState *elem = search_plan_tree(astate->appendplans[i],
210 return NULL; /* multiple matches */
217 * Result and Limit can be descended through (these are safe
218 * because they always return their input's current row)
222 return search_plan_tree(node->lefttree, table_oid);
225 * SubqueryScan too, but it keeps the child in a different place
227 case T_SubqueryScanState:
228 return search_plan_tree(((SubqueryScanState *) node)->subplan,
232 /* Otherwise, assume we can't descend through it */