aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2008-11-16 17:34:28 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2008-11-16 17:34:28 +0000
commit18004101acb98d8fefe7dda1c9f010cceff83b6d (patch)
tree2c9436b642ca63f2b831ba4e3cfd16d34436e6a2 /src
parent30f272a79b248fea1f25d63a3648d2660d370a69 (diff)
downloadpostgresql-18004101acb98d8fefe7dda1c9f010cceff83b6d.tar.gz
postgresql-18004101acb98d8fefe7dda1c9f010cceff83b6d.zip
Modify UPDATE/DELETE WHERE CURRENT OF to use the FOR UPDATE infrastructure to
locate the target row, if the cursor was declared with FOR UPDATE or FOR SHARE. This approach is more flexible and reliable than digging through the plan tree; for instance it can cope with join cursors. But we still provide the old code for use with non-FOR-UPDATE cursors. Per gripe from Robert Haas.
Diffstat (limited to 'src')
-rw-r--r--src/backend/executor/execCurrent.c152
-rw-r--r--src/backend/executor/execMain.c7
-rw-r--r--src/include/nodes/execnodes.h3
-rw-r--r--src/test/regress/expected/portals.out46
-rw-r--r--src/test/regress/sql/portals.sql22
5 files changed, 186 insertions, 44 deletions
diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c
index 5ae90c68f40..2f2270cfab3 100644
--- a/src/backend/executor/execCurrent.c
+++ b/src/backend/executor/execCurrent.c
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.7 2008/05/12 00:00:48 alvherre Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.8 2008/11/16 17:34:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -46,10 +46,6 @@ execCurrentOf(CurrentOfExpr *cexpr,
char *table_name;
Portal portal;
QueryDesc *queryDesc;
- ScanState *scanstate;
- bool lisnull;
- Oid tuple_tableoid;
- ItemPointer tuple_tid;
/* Get the cursor name --- may have to look up a parameter reference */
if (cexpr->cursor_name)
@@ -79,57 +75,129 @@ execCurrentOf(CurrentOfExpr *cexpr,
errmsg("cursor \"%s\" is not a SELECT query",
cursor_name)));
queryDesc = PortalGetQueryDesc(portal);
- if (queryDesc == NULL)
+ if (queryDesc == NULL || queryDesc->estate == 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.
+ * We have two different strategies depending on whether the cursor uses
+ * FOR UPDATE/SHARE or not. The reason for supporting both is that the
+ * FOR UPDATE code is able to identify a target table in many cases where
+ * the other code can't, while the non-FOR-UPDATE case allows use of WHERE
+ * CURRENT OF with an insensitive cursor.
*/
- 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)));
+ if (queryDesc->estate->es_rowMarks)
+ {
+ ExecRowMark *erm;
+ ListCell *lc;
- /*
- * 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)));
+ /*
+ * Here, the query must have exactly one FOR UPDATE/SHARE reference to
+ * the target table, and we dig the ctid info out of that.
+ */
+ erm = NULL;
+ foreach(lc, queryDesc->estate->es_rowMarks)
+ {
+ ExecRowMark *thiserm = (ExecRowMark *) lfirst(lc);
+
+ if (RelationGetRelid(thiserm->relation) == table_oid)
+ {
+ if (erm)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_STATE),
+ errmsg("cursor \"%s\" has multiple FOR UPDATE/SHARE references to table \"%s\"",
+ cursor_name, table_name)));
+ erm = thiserm;
+ }
+ }
- /* Now OK to return false if we found an inactive scan */
- if (TupIsNull(scanstate->ss_ScanTupleSlot))
+ if (erm == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_STATE),
+ errmsg("cursor \"%s\" does not have a FOR UPDATE/SHARE reference to table \"%s\"",
+ cursor_name, table_name)));
+
+ /*
+ * The cursor must have a current result row: per the SQL spec, it's
+ * an error if not.
+ */
+ if (portal->atStart || portal->atEnd)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_STATE),
+ errmsg("cursor \"%s\" is not positioned on a row",
+ cursor_name)));
+
+ /* Return the currently scanned TID, if there is one */
+ if (ItemPointerIsValid(&(erm->curCtid)))
+ {
+ *current_tid = erm->curCtid;
+ return true;
+ }
+
+ /*
+ * This table didn't produce the cursor's current row; some other
+ * inheritance child of the same parent must have. Signal caller
+ * to do nothing on this table.
+ */
return false;
+ }
+ else
+ {
+ ScanState *scanstate;
+ bool lisnull;
+ Oid tuple_tableoid;
+ ItemPointer tuple_tid;
+
+ /*
+ * Without FOR UPDATE, we 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)));
- /* Use slot_getattr to catch any possible mistakes */
- tuple_tableoid = DatumGetObjectId(slot_getattr(scanstate->ss_ScanTupleSlot,
- TableOidAttributeNumber,
- &lisnull));
- Assert(!lisnull);
- tuple_tid = (ItemPointer)
- DatumGetPointer(slot_getattr(scanstate->ss_ScanTupleSlot,
- SelfItemPointerAttributeNumber,
- &lisnull));
- Assert(!lisnull);
+ /*
+ * 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)));
- Assert(tuple_tableoid == table_oid);
+ /* Now OK to return false if we found an inactive scan */
+ if (TupIsNull(scanstate->ss_ScanTupleSlot))
+ return false;
- *current_tid = *tuple_tid;
+ /* Use slot_getattr to catch any possible mistakes */
+ tuple_tableoid =
+ DatumGetObjectId(slot_getattr(scanstate->ss_ScanTupleSlot,
+ TableOidAttributeNumber,
+ &lisnull));
+ Assert(!lisnull);
+ tuple_tid = (ItemPointer)
+ DatumGetPointer(slot_getattr(scanstate->ss_ScanTupleSlot,
+ SelfItemPointerAttributeNumber,
+ &lisnull));
+ Assert(!lisnull);
- return true;
+ Assert(tuple_tableoid == table_oid);
+
+ *current_tid = *tuple_tid;
+
+ return true;
+ }
}
/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 634ca69b4d1..6d389319fcc 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.316 2008/11/15 19:43:45 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.317 2008/11/16 17:34:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -609,6 +609,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
/* We'll locate the junk attrs below */
erm->ctidAttNo = InvalidAttrNumber;
erm->toidAttNo = InvalidAttrNumber;
+ ItemPointerSetInvalid(&(erm->curCtid));
estate->es_rowMarks = lappend(estate->es_rowMarks, erm);
}
@@ -1418,6 +1419,7 @@ lnext: ;
if (tableoid != RelationGetRelid(erm->relation))
{
/* this child is inactive right now */
+ ItemPointerSetInvalid(&(erm->curCtid));
continue;
}
}
@@ -1481,6 +1483,9 @@ lnext: ;
elog(ERROR, "unrecognized heap_lock_tuple status: %u",
test);
}
+
+ /* Remember tuple TID for WHERE CURRENT OF */
+ erm->curCtid = tuple.t_self;
}
}
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8c8742e286e..9aae040019b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.195 2008/11/15 19:43:46 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.196 2008/11/16 17:34:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -381,6 +381,7 @@ typedef struct ExecRowMark
bool noWait; /* NOWAIT option */
AttrNumber ctidAttNo; /* resno of its ctid junk attribute */
AttrNumber toidAttNo; /* resno of tableoid junk attribute, if any */
+ ItemPointerData curCtid; /* ctid of currently locked tuple, if any */
} ExecRowMark;
diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out
index 66563615d88..95dcea5a1d9 100644
--- a/src/test/regress/expected/portals.out
+++ b/src/test/regress/expected/portals.out
@@ -1154,6 +1154,47 @@ SELECT * FROM uctest;
110 | hundred
(3 rows)
+-- Can update from a self-join, but only if FOR UPDATE says which to use
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5;
+FETCH 1 FROM c1;
+ f1 | f2 | f1 | f2
+----+-----+----+-------
+ 18 | one | 13 | three
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail
+ERROR: cursor "c1" is not a simply updatable scan of table "uctest"
+ROLLBACK;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5 FOR UPDATE;
+FETCH 1 FROM c1;
+ f1 | f2 | f1 | f2
+----+-----+----+-------
+ 18 | one | 13 | three
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail
+ERROR: cursor "c1" has multiple FOR UPDATE/SHARE references to table "uctest"
+ROLLBACK;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5 FOR SHARE OF a;
+FETCH 1 FROM c1;
+ f1 | f2 | f1 | f2
+----+-----+----+-------
+ 18 | one | 13 | three
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+ f1 | f2
+-----+---------
+ 13 | three
+ 28 | one
+ 110 | hundred
+(3 rows)
+
+ROLLBACK;
-- Check various error cases
DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no such cursor
ERROR: cursor "c1" does not exist
@@ -1166,6 +1207,11 @@ DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor on wrong table
ERROR: cursor "c" is not a simply updatable scan of table "uctest"
ROLLBACK;
BEGIN;
+DECLARE c CURSOR FOR SELECT * FROM tenk2 FOR SHARE;
+DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor on wrong table
+ERROR: cursor "c" does not have a FOR UPDATE/SHARE reference to table "uctest"
+ROLLBACK;
+BEGIN;
DECLARE c CURSOR FOR SELECT * FROM tenk1 JOIN tenk2 USING (unique1);
DELETE FROM tenk1 WHERE CURRENT OF c; -- fail, cursor is on a join
ERROR: cursor "c" is not a simply updatable scan of table "tenk1"
diff --git a/src/test/regress/sql/portals.sql b/src/test/regress/sql/portals.sql
index b53eaac786a..4265aaa43cf 100644
--- a/src/test/regress/sql/portals.sql
+++ b/src/test/regress/sql/portals.sql
@@ -404,6 +404,24 @@ FETCH 1 FROM c1;
COMMIT;
SELECT * FROM uctest;
+-- Can update from a self-join, but only if FOR UPDATE says which to use
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5;
+FETCH 1 FROM c1;
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail
+ROLLBACK;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5 FOR UPDATE;
+FETCH 1 FROM c1;
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail
+ROLLBACK;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5 FOR SHARE OF a;
+FETCH 1 FROM c1;
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+ROLLBACK;
+
-- Check various error cases
DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no such cursor
@@ -414,6 +432,10 @@ DECLARE c CURSOR FOR SELECT * FROM tenk2;
DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor on wrong table
ROLLBACK;
BEGIN;
+DECLARE c CURSOR FOR SELECT * FROM tenk2 FOR SHARE;
+DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor on wrong table
+ROLLBACK;
+BEGIN;
DECLARE c CURSOR FOR SELECT * FROM tenk1 JOIN tenk2 USING (unique1);
DELETE FROM tenk1 WHERE CURRENT OF c; -- fail, cursor is on a join
ROLLBACK;