1 /*-------------------------------------------------------------------------
4 * executor support for WHERE CURRENT OF cursor
6 * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
11 *-------------------------------------------------------------------------
15 #include "access/sysattr.h"
16 #include "catalog/pg_type.h"
17 #include "executor/executor.h"
18 #include "utils/builtins.h"
19 #include "utils/lsyscache.h"
20 #include "utils/portal.h"
23 static char *fetch_param_value(ExprContext
*econtext
, int paramId
);
24 static ScanState
*search_plan_tree(PlanState
*node
, Oid table_oid
);
30 * Given a CURRENT OF expression and the OID of a table, determine which row
31 * of the table is currently being scanned by the cursor named by CURRENT OF,
32 * and return the row's TID into *current_tid.
34 * Returns TRUE if a row was identified. Returns FALSE if the cursor is valid
35 * for the table but is not currently scanning a row of the table (this is a
36 * legal situation in inheritance cases). Raises error if cursor is not a
37 * valid updatable scan of the specified table.
40 execCurrentOf(CurrentOfExpr
*cexpr
,
41 ExprContext
*econtext
,
43 ItemPointer current_tid
)
52 ItemPointer tuple_tid
;
54 /* Get the cursor name --- may have to look up a parameter reference */
55 if (cexpr
->cursor_name
)
56 cursor_name
= cexpr
->cursor_name
;
58 cursor_name
= fetch_param_value(econtext
, cexpr
->cursor_param
);
60 /* Fetch table name for possible use in error messages */
61 table_name
= get_rel_name(table_oid
);
62 if (table_name
== NULL
)
63 elog(ERROR
, "cache lookup failed for relation %u", table_oid
);
65 /* Find the cursor's portal */
66 portal
= GetPortalByName(cursor_name
);
67 if (!PortalIsValid(portal
))
69 (errcode(ERRCODE_UNDEFINED_CURSOR
),
70 errmsg("cursor \"%s\" does not exist", cursor_name
)));
73 * We have to watch out for non-SELECT queries as well as held cursors,
74 * both of which may have null queryDesc.
76 if (portal
->strategy
!= PORTAL_ONE_SELECT
)
78 (errcode(ERRCODE_INVALID_CURSOR_STATE
),
79 errmsg("cursor \"%s\" is not a SELECT query",
81 queryDesc
= PortalGetQueryDesc(portal
);
82 if (queryDesc
== NULL
)
84 (errcode(ERRCODE_INVALID_CURSOR_STATE
),
85 errmsg("cursor \"%s\" is held from a previous transaction",
89 * Dig through the cursor's plan to find the scan node. Fail if it's not
90 * there or buried underneath aggregation.
92 scanstate
= search_plan_tree(ExecGetActivePlanTree(queryDesc
),
96 (errcode(ERRCODE_INVALID_CURSOR_STATE
),
97 errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
98 cursor_name
, table_name
)));
101 * The cursor must have a current result row: per the SQL spec, it's an
102 * error if not. We test this at the top level, rather than at the scan
103 * node level, because in inheritance cases any one table scan could
104 * easily not be on a row. We want to return false, not raise error, if
105 * the passed-in table OID is for one of the inactive scans.
107 if (portal
->atStart
|| portal
->atEnd
)
109 (errcode(ERRCODE_INVALID_CURSOR_STATE
),
110 errmsg("cursor \"%s\" is not positioned on a row",
113 /* Now OK to return false if we found an inactive scan */
114 if (TupIsNull(scanstate
->ss_ScanTupleSlot
))
117 /* Use slot_getattr to catch any possible mistakes */
118 tuple_tableoid
= DatumGetObjectId(slot_getattr(scanstate
->ss_ScanTupleSlot
,
119 TableOidAttributeNumber
,
122 tuple_tid
= (ItemPointer
)
123 DatumGetPointer(slot_getattr(scanstate
->ss_ScanTupleSlot
,
124 SelfItemPointerAttributeNumber
,
128 Assert(tuple_tableoid
== table_oid
);
130 *current_tid
= *tuple_tid
;
138 * Fetch the string value of a param, verifying it is of type REFCURSOR.
141 fetch_param_value(ExprContext
*econtext
, int paramId
)
143 ParamListInfo paramInfo
= econtext
->ecxt_param_list_info
;
146 paramId
> 0 && paramId
<= paramInfo
->numParams
)
148 ParamExternData
*prm
= ¶mInfo
->params
[paramId
- 1];
150 if (OidIsValid(prm
->ptype
) && !prm
->isnull
)
152 Assert(prm
->ptype
== REFCURSOROID
);
153 /* We know that refcursor uses text's I/O routines */
154 return TextDatumGetCString(prm
->value
);
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 */