aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
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;