mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
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.
This commit is contained in:
@ -4,7 +4,7 @@
|
||||
# Makefile for executor
|
||||
#
|
||||
# IDENTIFICATION
|
||||
# $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.25 2007/01/20 17:16:11 petere Exp $
|
||||
# $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.26 2007/06/11 01:16:22 tgl Exp $
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@ -12,7 +12,7 @@ subdir = src/backend/executor
|
||||
top_builddir = ../../..
|
||||
include $(top_builddir)/src/Makefile.global
|
||||
|
||||
OBJS = execAmi.o execGrouping.o execJunk.o execMain.o \
|
||||
OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
|
||||
execProcnode.o execQual.o execScan.o execTuples.o \
|
||||
execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
|
||||
nodeBitmapAnd.o nodeBitmapOr.o \
|
||||
|
185
src/backend/executor/execCurrent.c
Normal file
185
src/backend/executor/execCurrent.c
Normal file
@ -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;
|
||||
}
|
@ -26,7 +26,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.294 2007/06/03 17:07:00 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.295 2007/06/11 01:16:22 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -2368,6 +2368,24 @@ EvalPlanQualStop(evalPlanQual *epq)
|
||||
epq->planstate = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* ExecGetActivePlanTree --- get the active PlanState tree from a QueryDesc
|
||||
*
|
||||
* Ordinarily this is just the one mentioned in the QueryDesc, but if we
|
||||
* are looking at a row returned by the EvalPlanQual machinery, we need
|
||||
* to look at the subsidiary state instead.
|
||||
*/
|
||||
PlanState *
|
||||
ExecGetActivePlanTree(QueryDesc *queryDesc)
|
||||
{
|
||||
EState *estate = queryDesc->estate;
|
||||
|
||||
if (estate && estate->es_useEvalPlan && estate->es_evalPlanQual != NULL)
|
||||
return estate->es_evalPlanQual->planstate;
|
||||
else
|
||||
return queryDesc->planstate;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Support for SELECT INTO (a/k/a CREATE TABLE AS)
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.218 2007/06/05 21:31:04 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.219 2007/06/11 01:16:22 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -151,6 +151,8 @@ static Datum ExecEvalCoerceViaIO(CoerceViaIOState *iostate,
|
||||
static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
|
||||
ExprContext *econtext,
|
||||
bool *isNull, ExprDoneCond *isDone);
|
||||
static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
|
||||
bool *isNull, ExprDoneCond *isDone);
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
@ -3618,6 +3620,41 @@ ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
|
||||
astate->amstate);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecEvalCurrentOfExpr
|
||||
*
|
||||
* Normally, the planner will convert CURRENT OF into a TidScan qualification,
|
||||
* but we have plain execQual support in case it doesn't.
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
static Datum
|
||||
ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
|
||||
bool *isNull, ExprDoneCond *isDone)
|
||||
{
|
||||
CurrentOfExpr *cexpr = (CurrentOfExpr *) exprstate->expr;
|
||||
bool result;
|
||||
HeapTuple tup;
|
||||
ItemPointerData cursor_tid;
|
||||
|
||||
if (isDone)
|
||||
*isDone = ExprSingleResult;
|
||||
*isNull = false;
|
||||
|
||||
Assert(cexpr->cvarno != INNER);
|
||||
Assert(cexpr->cvarno != OUTER);
|
||||
Assert(!TupIsNull(econtext->ecxt_scantuple));
|
||||
tup = econtext->ecxt_scantuple->tts_tuple;
|
||||
if (tup == NULL)
|
||||
elog(ERROR, "CURRENT OF applied to non-materialized tuple");
|
||||
|
||||
if (execCurrentOf(cexpr->cursor_name, tup->t_tableOid, &cursor_tid))
|
||||
result = ItemPointerEquals(&cursor_tid, &(tup->t_self));
|
||||
else
|
||||
result = false;
|
||||
|
||||
return BoolGetDatum(result);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecEvalExprSwitchContext
|
||||
@ -4266,6 +4303,10 @@ ExecInitExpr(Expr *node, PlanState *parent)
|
||||
state = (ExprState *) cstate;
|
||||
}
|
||||
break;
|
||||
case T_CurrentOfExpr:
|
||||
state = (ExprState *) makeNode(ExprState);
|
||||
state->evalfunc = ExecEvalCurrentOfExpr;
|
||||
break;
|
||||
case T_TargetEntry:
|
||||
{
|
||||
TargetEntry *tle = (TargetEntry *) node;
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/executor/nodeTidscan.c,v 1.53 2007/01/05 22:19:28 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/executor/nodeTidscan.c,v 1.54 2007/06/11 01:16:22 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -61,8 +61,8 @@ TidListCreate(TidScanState *tidstate)
|
||||
|
||||
/*
|
||||
* We initialize the array with enough slots for the case that all quals
|
||||
* are simple OpExprs. If there's any ScalarArrayOpExprs, we may have to
|
||||
* enlarge the array.
|
||||
* are simple OpExprs or CurrentOfExprs. If there are any
|
||||
* ScalarArrayOpExprs, we may have to enlarge the array.
|
||||
*/
|
||||
numAllocTids = list_length(evalList);
|
||||
tidList = (ItemPointerData *)
|
||||
@ -148,6 +148,25 @@ TidListCreate(TidScanState *tidstate)
|
||||
pfree(ipdatums);
|
||||
pfree(ipnulls);
|
||||
}
|
||||
else if (expr && IsA(expr, CurrentOfExpr))
|
||||
{
|
||||
CurrentOfExpr *cexpr = (CurrentOfExpr *) expr;
|
||||
ItemPointerData cursor_tid;
|
||||
|
||||
if (execCurrentOf(cexpr->cursor_name,
|
||||
RelationGetRelid(tidstate->ss.ss_currentRelation),
|
||||
&cursor_tid))
|
||||
{
|
||||
if (numTids >= numAllocTids)
|
||||
{
|
||||
numAllocTids *= 2;
|
||||
tidList = (ItemPointerData *)
|
||||
repalloc(tidList,
|
||||
numAllocTids * sizeof(ItemPointerData));
|
||||
}
|
||||
tidList[numTids++] = cursor_tid;
|
||||
}
|
||||
}
|
||||
else
|
||||
elog(ERROR, "could not identify CTID expression");
|
||||
}
|
||||
|
Reference in New Issue
Block a user