diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2007-06-11 01:16:30 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2007-06-11 01:16:30 +0000 |
commit | 6808f1b1de0ebcd4af558ba84c3226b2027f55ea (patch) | |
tree | ebd12580d3aaca6ec79b5d99563a1eff02451e88 /src/backend/executor/execCurrent.c | |
parent | 85d72f05167b87bc44464b2eabea8538f1fd1e45 (diff) | |
download | postgresql-6808f1b1de0ebcd4af558ba84c3226b2027f55ea.tar.gz postgresql-6808f1b1de0ebcd4af558ba84c3226b2027f55ea.zip |
Support UPDATE/DELETE WHERE CURRENT OF cursor_name, per SQL standard.
Along the way, allow FOR UPDATE in non-WITH-HOLD cursors; there may once
have been a reason to disallow that, but it seems to work now, and it's
really rather necessary if you want to select a row via a cursor and then
update it in a concurrent-safe fashion.
Original patch by Arul Shaji, rather heavily editorialized by Tom Lane.
Diffstat (limited to 'src/backend/executor/execCurrent.c')
-rw-r--r-- | src/backend/executor/execCurrent.c | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c new file mode 100644 index 00000000000..ce95d58b81b --- /dev/null +++ b/src/backend/executor/execCurrent.c @@ -0,0 +1,185 @@ +/*------------------------------------------------------------------------- + * + * execCurrent.c + * executor support for WHERE CURRENT OF cursor + * + * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.1 2007/06/11 01:16:22 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "executor/executor.h" +#include "utils/lsyscache.h" +#include "utils/portal.h" + + +static ScanState *search_plan_tree(PlanState *node, Oid table_oid); + + +/* + * execCurrentOf + * + * Given the name of a cursor and the OID of a table, determine which row + * of the table is currently being scanned by the cursor, and return its + * TID into *current_tid. + * + * Returns TRUE if a row was identified. Returns FALSE if the cursor is valid + * for the table but is not currently scanning a row of the table (this is a + * legal situation in inheritance cases). Raises error if cursor is not a + * valid updatable scan of the specified table. + */ +bool +execCurrentOf(char *cursor_name, Oid table_oid, + ItemPointer current_tid) +{ + char *table_name; + Portal portal; + QueryDesc *queryDesc; + ScanState *scanstate; + HeapTuple tup; + + /* Fetch table name for possible use in error messages */ + table_name = get_rel_name(table_oid); + if (table_name == NULL) + elog(ERROR, "cache lookup failed for relation %u", table_oid); + + /* Find the cursor's portal */ + portal = GetPortalByName(cursor_name); + if (!PortalIsValid(portal)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_CURSOR), + errmsg("cursor \"%s\" does not exist", cursor_name))); + + /* + * We have to watch out for non-SELECT queries as well as held cursors, + * both of which may have null queryDesc. + */ + if (portal->strategy != PORTAL_ONE_SELECT) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_STATE), + errmsg("cursor \"%s\" is not a SELECT query", + cursor_name))); + queryDesc = PortalGetQueryDesc(portal); + if (queryDesc == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_STATE), + errmsg("cursor \"%s\" is held from a previous transaction", + cursor_name))); + + /* + * Dig through the cursor's plan to find the scan node. Fail if it's + * not there or buried underneath aggregation. + */ + scanstate = search_plan_tree(ExecGetActivePlanTree(queryDesc), + table_oid); + if (!scanstate) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_STATE), + errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"", + cursor_name, table_name))); + + /* + * The cursor must have a current result row: per the SQL spec, it's + * an error if not. We test this at the top level, rather than at + * the scan node level, because in inheritance cases any one table + * scan could easily not be on a row. We want to return false, not + * raise error, if the passed-in table OID is for one of the inactive + * scans. + */ + if (portal->atStart || portal->atEnd) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_STATE), + errmsg("cursor \"%s\" is not positioned on a row", + cursor_name))); + + /* Now OK to return false if we found an inactive scan */ + if (TupIsNull(scanstate->ss_ScanTupleSlot)) + return false; + + tup = scanstate->ss_ScanTupleSlot->tts_tuple; + if (tup == NULL) + elog(ERROR, "CURRENT OF applied to non-materialized tuple"); + Assert(tup->t_tableOid == table_oid); + + *current_tid = tup->t_self; + + return true; +} + +/* + * search_plan_tree + * + * Search through a PlanState tree for a scan node on the specified table. + * Return NULL if not found or multiple candidates. + */ +static ScanState * +search_plan_tree(PlanState *node, Oid table_oid) +{ + if (node == NULL) + return NULL; + switch (nodeTag(node)) + { + /* + * scan nodes can all be treated alike + */ + case T_SeqScanState: + case T_IndexScanState: + case T_BitmapHeapScanState: + case T_TidScanState: + { + ScanState *sstate = (ScanState *) node; + + if (RelationGetRelid(sstate->ss_currentRelation) == table_oid) + return sstate; + break; + } + + /* + * For Append, we must look through the members; watch out for + * multiple matches (possible if it was from UNION ALL) + */ + case T_AppendState: + { + AppendState *astate = (AppendState *) node; + ScanState *result = NULL; + int i; + + for (i = 0; i < astate->as_nplans; i++) + { + ScanState *elem = search_plan_tree(astate->appendplans[i], + table_oid); + + if (!elem) + continue; + if (result) + return NULL; /* multiple matches */ + result = elem; + } + return result; + } + + /* + * Result and Limit can be descended through (these are safe + * because they always return their input's current row) + */ + case T_ResultState: + case T_LimitState: + return search_plan_tree(node->lefttree, table_oid); + + /* + * SubqueryScan too, but it keeps the child in a different place + */ + case T_SubqueryScanState: + return search_plan_tree(((SubqueryScanState *) node)->subplan, + table_oid); + + default: + /* Otherwise, assume we can't descend through it */ + break; + } + return NULL; +} |