aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands')
-rw-r--r--src/backend/commands/cluster.c8
-rw-r--r--src/backend/commands/tablecmds.c50
-rw-r--r--src/backend/commands/trigger.c45
3 files changed, 100 insertions, 3 deletions
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 3256b172206..19ea0cac1ae 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.116.2.3 2007/09/12 15:16:24 alvherre Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.116.2.4 2008/05/27 21:14:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -398,6 +398,12 @@ cluster_rel(RelToCluster *rvtc, bool recheck)
errmsg("\"%s\" is a system catalog",
RelationGetRelationName(OldHeap))));
+ /*
+ * Also check for active uses of the relation in the current transaction,
+ * including open scans and pending AFTER trigger events.
+ */
+ CheckTableNotInUse(OldHeap, "CLUSTER");
+
/* Drop relcache refcnt on OldIndex, but keep lock */
index_close(OldIndex);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 97caafbb8e1..ccc25c88f4a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.91.2.3 2008/05/09 22:38:05 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.91.2.4 2008/05/27 21:14:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -398,6 +398,12 @@ TruncateRelation(const RangeVar *relation)
errmsg("cannot truncate temporary tables of other sessions")));
/*
+ * Also check for active uses of the relation in the current
+ * transaction, including open scans and pending AFTER trigger events.
+ */
+ CheckTableNotInUse(rel, "TRUNCATE");
+
+ /*
* Don't allow truncate on tables which are referenced by foreign keys
*/
heap_truncate_check_FKs(rel);
@@ -1592,6 +1598,48 @@ update_ri_trigger_args(Oid relid,
CommandCounterIncrement();
}
+/*
+ * Disallow TRUNCATE (and similar commands) when the current backend has
+ * any open reference to the target table besides the one just acquired by
+ * the calling command; this implies there's an open cursor or active plan.
+ * We need this check because our AccessExclusiveLock doesn't protect us
+ * against stomping on our own foot, only other people's feet!
+ *
+ * We also reject these commands if there are any pending AFTER trigger events
+ * for the rel. This is certainly necessary for CLUSTER, because it does not
+ * preserve tuple TIDs and so the pending events would try to fetch the wrong
+ * tuples. It might be overly cautious in other cases, but again it seems
+ * better to err on the side of paranoia.
+ *
+ * REINDEX calls this with "rel" referencing the index to be rebuilt; here
+ * we are worried about active indexscans on the index. The trigger-event
+ * check can be skipped, since we are doing no damage to the parent table.
+ *
+ * The statement name (eg, "TRUNCATE") is passed for use in error messages.
+ */
+void
+CheckTableNotInUse(Relation rel, const char *stmt)
+{
+ int expected_refcnt;
+
+ expected_refcnt = rel->rd_isnailed ? 2 : 1;
+ if (rel->rd_refcnt != expected_refcnt)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ /* translator: first %s is a SQL command, eg ALTER TABLE */
+ errmsg("cannot %s \"%s\" because "
+ "it is being used by active queries in this session",
+ stmt, RelationGetRelationName(rel))));
+
+ if (rel->rd_rel->relkind != RELKIND_INDEX &&
+ AfterTriggerPendingOnRel(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ /* translator: first %s is a SQL command, eg ALTER TABLE */
+ errmsg("cannot %s \"%s\" because "
+ "it has pending trigger events",
+ stmt, RelationGetRelationName(rel))));
+}
/* ----------------
* AlterTableAddColumn
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 4b9d5efea98..9d2b2b64e98 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.159.2.2 2006/01/12 21:49:31 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.159.2.3 2008/05/27 21:14:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -2365,6 +2365,49 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
deferredTriggers->deftrig_events_imm = NULL;
}
+/* ----------
+ * AfterTriggerPendingOnRel()
+ * Test to see if there are any pending after-trigger events for rel.
+ *
+ * This is used by TRUNCATE, CLUSTER, ALTER TABLE, etc to detect whether
+ * it is unsafe to perform major surgery on a relation. Note that only
+ * local pending events are examined. We assume that having exclusive lock
+ * on a rel guarantees there are no unserviced events in other backends ---
+ * but having a lock does not prevent there being such events in our own.
+ *
+ * In some scenarios it'd be reasonable to remove pending events (more
+ * specifically, mark them DONE by the current subxact) but without a lot
+ * of knowledge of the trigger semantics we can't do this in general.
+ * ----------
+ */
+bool
+AfterTriggerPendingOnRel(Oid relid)
+{
+ DeferredTriggerEvent event;
+
+ /* No-op if we aren't in a transaction. (Shouldn't happen?) */
+ if (deferredTriggers == NULL)
+ return false;
+
+ /* Scan queued events */
+ for (event = deferredTriggers->deftrig_events;
+ event != NULL;
+ event = event->dte_next)
+ {
+ /*
+ * We can ignore completed events.
+ */
+ if (event->dte_event & (TRIGGER_DEFERRED_DONE |
+ TRIGGER_DEFERRED_CANCELED))
+ continue;
+
+ if (event->dte_relid == relid)
+ return true;
+ }
+
+ return false;
+}
+
/* ----------
* DeferredTriggerSaveEvent()