mirror of https://github.com/postgres/postgres
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.REL8_3_STABLE
parent
85d72f0516
commit
6808f1b1de
@ -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; |
||||
} |
Loading…
Reference in new issue