*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.238 2008/01/01 19:45:49 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.239 2008/01/02 23:34:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
Relation rel, Relation pkrel, Oid constraintOid);
static void createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
Oid constraintOid);
+static void CheckTableNotInUse(Relation rel);
static void ATController(Relation rel, List *cmds, bool recurse);
static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
bool recurse, bool recursing);
heap_truncate_check_FKs(rels, false);
#endif
- /*
- * Also check for pending AFTER trigger events on the target relations. We
- * can't just leave those be, since they will try to fetch tuples that the
- * TRUNCATE removes.
- */
- AfterTriggerCheckTruncate(relids);
-
/*
* OK, truncate each table.
*/
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot truncate temporary tables of other sessions")));
+
+ /*
+ * Also check for pending AFTER trigger events on the relation. We can't
+ * just leave those be, since they will try to fetch tuples that the
+ * TRUNCATE removes.
+ */
+ if (AfterTriggerPendingOnRel(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("cannot truncate table \"%s\" because it has pending trigger events",
+ RelationGetRelationName(rel))));
}
/*----------
AlterTable(AlterTableStmt *stmt)
{
Relation rel = relation_openrv(stmt->relation, AccessExclusiveLock);
+
+ CheckTableNotInUse(rel);
+
+ ATController(rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt));
+}
+
+/*
+ * Disallow ALTER TABLE when the current backend has any open reference to
+ * it besides the one we just got (such as an open cursor or active plan);
+ * our AccessExclusiveLock doesn't protect us against stomping on our own
+ * foot, only other people's feet!
+ *
+ * Note: the only case known to cause serious trouble is ALTER COLUMN TYPE,
+ * and some changes are obviously pretty benign, so this could possibly be
+ * relaxed to only error out for certain types of alterations. But the
+ * use-case for allowing any of these things is not obvious, so we won't
+ * work hard at it for now.
+ *
+ * We also reject ALTER TABLE if there are any pending AFTER trigger events
+ * for the rel. This is certainly necessary for the rewriting variants of
+ * ALTER TABLE, because they don't 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.
+ */
+static void
+CheckTableNotInUse(Relation rel)
+{
int expected_refcnt;
- /*
- * Disallow ALTER TABLE when the current backend has any open reference to
- * it besides the one we just got (such as an open cursor or active plan);
- * our AccessExclusiveLock doesn't protect us against stomping on our own
- * foot, only other people's feet!
- *
- * Note: the only case known to cause serious trouble is ALTER COLUMN
- * TYPE, and some changes are obviously pretty benign, so this could
- * possibly be relaxed to only error out for certain types of alterations.
- * But the use-case for allowing any of these things is not obvious, so we
- * won't work hard at it for now.
- */
expected_refcnt = rel->rd_isnailed ? 2 : 1;
if (rel->rd_refcnt != expected_refcnt)
ereport(ERROR,
errmsg("relation \"%s\" is being used by active queries in this session",
RelationGetRelationName(rel))));
- ATController(rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt));
+ if (AfterTriggerPendingOnRel(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("cannot alter table \"%s\" because it has pending trigger events",
+ RelationGetRelationName(rel))));
}
/*
* We do not reject if the relation is already open, because it's quite
* likely that one or more layers of caller have it open. That means it
* is unsafe to use this entry point for alterations that could break
- * existing query plans.
+ * existing query plans. On the assumption it's not used for such, we
+ * don't have to reject pending AFTER triggers, either.
*/
void
AlterTableInternal(Oid relid, List *cmds, bool recurse)
if (childrelid == relid)
continue;
childrel = relation_open(childrelid, AccessExclusiveLock);
- /* check for child relation in use in this session */
- if (childrel->rd_refcnt != 1)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_IN_USE),
- errmsg("relation \"%s\" is being used by active queries in this session",
- RelationGetRelationName(childrel))));
+ CheckTableNotInUse(childrel);
ATPrepCmd(wqueue, childrel, cmd, false, true);
relation_close(childrel, NoLock);
}
Relation childrel;
childrel = relation_open(childrelid, AccessExclusiveLock);
- /* check for child relation in use in this session */
- if (childrel->rd_refcnt != 1)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_IN_USE),
- errmsg("relation \"%s\" is being used by active queries in this session",
- RelationGetRelationName(childrel))));
+ CheckTableNotInUse(childrel);
ATPrepCmd(wqueue, childrel, cmd, true, true);
relation_close(childrel, NoLock);
}
Form_pg_attribute childatt;
childrel = heap_open(childrelid, AccessExclusiveLock);
- /* check for child relation in use in this session */
- if (childrel->rd_refcnt != 1)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_IN_USE),
- errmsg("relation \"%s\" is being used by active queries in this session",
- RelationGetRelationName(childrel))));
+ CheckTableNotInUse(childrel);
tuple = SearchSysCacheCopyAttName(childrelid, colName);
if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.226 2008/01/01 19:45:49 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.227 2008/01/02 23:34:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
}
/* ----------
- * AfterTriggerCheckTruncate()
- * Test deferred-trigger status to see if a TRUNCATE is OK.
+ * AfterTriggerPendingOnRel()
+ * Test to see if there are any pending after-trigger events for rel.
*
- * The argument is a list of OIDs of relations due to be truncated.
- * We raise error if there are any pending after-trigger events for them.
+ * 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.
* ----------
*/
-void
-AfterTriggerCheckTruncate(List *relids)
+bool
+AfterTriggerPendingOnRel(Oid relid)
{
AfterTriggerEvent event;
int depth;
- /*
- * Ignore call if we aren't in a transaction. (Shouldn't happen?)
- */
+ /* No-op if we aren't in a transaction. (Shouldn't happen?) */
if (afterTriggers == NULL)
- return;
+ return false;
/* Scan queued events */
for (event = afterTriggers->events.head;
/*
* We can ignore completed events. (Even if a DONE flag is rolled
* back by subxact abort, it's OK because the effects of the TRUNCATE
- * must get rolled back too.)
+ * or whatever must get rolled back too.)
*/
if (event->ate_event & AFTER_TRIGGER_DONE)
continue;
- if (list_member_oid(relids, event->ate_relid))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot truncate table \"%s\" because it has pending trigger events",
- get_rel_name(event->ate_relid))));
+ if (event->ate_relid == relid)
+ return true;
}
/*
* Also scan events queued by incomplete queries. This could only matter
- * if a TRUNCATE is executed by a function or trigger within an updating
+ * if TRUNCATE/etc is executed by a function or trigger within an updating
* query on the same relation, which is pretty perverse, but let's check.
*/
for (depth = 0; depth <= afterTriggers->query_depth; depth++)
if (event->ate_event & AFTER_TRIGGER_DONE)
continue;
- if (list_member_oid(relids, event->ate_relid))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot truncate table \"%s\" because it has pending trigger events",
- get_rel_name(event->ate_relid))));
+ if (event->ate_relid == relid)
+ return true;
}
}
+
+ return false;
}