diff options
-rw-r--r-- | src/backend/catalog/index.c | 19 | ||||
-rw-r--r-- | src/backend/commands/cluster.c | 8 | ||||
-rw-r--r-- | src/backend/commands/tablecmds.c | 101 | ||||
-rw-r--r-- | src/backend/commands/trigger.c | 66 | ||||
-rw-r--r-- | src/include/commands/tablecmds.h | 5 | ||||
-rw-r--r-- | src/include/commands/trigger.h | 4 |
6 files changed, 158 insertions, 45 deletions
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 1e9b6367924..8b7b0bf1e5d 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.244.4.3 2008/01/03 21:25:00 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.244.4.4 2008/05/27 21:13:50 tgl Exp $ * * * INTERFACE ROUTINES @@ -32,9 +32,11 @@ #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/indexing.h" +#include "catalog/namespace.h" #include "catalog/pg_constraint.h" #include "catalog/pg_opclass.h" #include "catalog/pg_type.h" +#include "commands/tablecmds.h" #include "executor/executor.h" #include "miscadmin.h" #include "optimizer/clauses.h" @@ -1662,6 +1664,21 @@ reindex_index(Oid indexId) LockRelation(iRel, AccessExclusiveLock); /* + * Don't allow reindex on temp tables of other backends ... their local + * buffer manager is not going to cope. + */ + if (isOtherTempNamespace(RelationGetNamespace(iRel))) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot reindex temporary tables of other sessions"))); + + /* + * Also check for active uses of the index in the current transaction; + * we don't want to reindex underneath an open indexscan. + */ + CheckTableNotInUse(iRel, "REINDEX INDEX"); + + /* * If it's a shared index, we must do inplace processing (because we * have no way to update relfilenode in other databases). Otherwise * we can do it the normal transaction-safe way. diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 5cc6aca59d3..e570ca861d9 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.131.4.2 2007/09/12 15:16:23 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.131.4.3 2008/05/27 21:13:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -405,6 +405,12 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot cluster 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(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 df11fe0e10e..8001b0798c4 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.142.4.9 2008/05/09 22:37:56 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.142.4.10 2008/05/27 21:13:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -578,6 +578,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); @@ -1777,6 +1783,55 @@ update_ri_trigger_args(Oid relid, } /* + * Disallow ALTER TABLE (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! + * + * For ALTER TABLE, 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 these commands 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. + * + * 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, "ALTER TABLE") 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)))); +} + +/* * AlterTable * Execute ALTER TABLE, which can be a list of subcommands * @@ -1813,26 +1868,8 @@ void AlterTable(AlterTableStmt *stmt) { Relation rel = relation_openrv(stmt->relation, AccessExclusiveLock); - 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, - (errcode(ERRCODE_OBJECT_IN_USE), - errmsg("relation \"%s\" is being used by active queries in this session", - RelationGetRelationName(rel)))); + CheckTableNotInUse(rel, "ALTER TABLE"); ATController(rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt)); } @@ -1845,7 +1882,8 @@ AlterTable(AlterTableStmt *stmt) * 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) @@ -2739,12 +2777,7 @@ ATSimpleRecursion(List **wqueue, Relation rel, 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, "ALTER TABLE"); ATPrepCmd(wqueue, childrel, cmd, false, true); relation_close(childrel, NoLock); } @@ -2776,12 +2809,7 @@ ATOneLevelRecursion(List **wqueue, Relation rel, 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, "ALTER TABLE"); ATPrepCmd(wqueue, childrel, cmd, true, true); relation_close(childrel, NoLock); } @@ -3610,12 +3638,7 @@ ATExecDropColumn(Relation rel, const char *colName, 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, "ALTER TABLE"); tuple = SearchSysCacheCopyAttName(childrelid, colName); if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 14f8980a99e..01018819010 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 - * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.177.4.4 2007/08/15 19:16:12 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.177.4.5 2008/05/27 21:13:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2851,6 +2851,70 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt) } } +/* ---------- + * 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) +{ + AfterTriggerEvent event; + int depth; + + /* No-op if we aren't in a transaction. (Shouldn't happen?) */ + if (afterTriggers == NULL) + return false; + + /* Scan queued events */ + for (event = afterTriggers->events.head; + event != NULL; + event = event->ate_next) + { + /* + * 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 + * or whatever must get rolled back too.) + */ + if (event->ate_event & AFTER_TRIGGER_DONE) + continue; + + if (event->ate_relid == relid) + return true; + } + + /* + * Also scan events queued by incomplete queries. This could only matter + * 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++) + { + for (event = afterTriggers->query_stack[depth].head; + event != NULL; + event = event->ate_next) + { + if (event->ate_event & AFTER_TRIGGER_DONE) + continue; + + if (event->ate_relid == relid) + return true; + } + } + + return false; +} + /* ---------- * AfterTriggerSaveEvent() diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index 75da81f0000..2f77ce47f6f 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/tablecmds.h,v 1.21 2004/12/31 22:03:28 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/commands/tablecmds.h,v 1.21.4.1 2008/05/27 21:13:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -15,6 +15,7 @@ #define TABLECMDS_H #include "nodes/parsenodes.h" +#include "utils/rel.h" extern Oid DefineRelation(CreateStmt *stmt, char relkind); @@ -27,6 +28,8 @@ extern void AlterTableInternal(Oid relid, List *cmds, bool recurse); extern void AlterTableCreateToastTable(Oid relOid, bool silent); +extern void CheckTableNotInUse(Relation rel, const char *stmt); + extern void TruncateRelation(const RangeVar *relation); extern void renameatt(Oid myrelid, diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index f167da81ff5..4ae2ac85822 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.51.4.1 2005/04/11 19:51:32 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.51.4.2 2008/05/27 21:13:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -161,8 +161,8 @@ extern void AfterTriggerFireDeferred(void); extern void AfterTriggerEndXact(bool isCommit); extern void AfterTriggerBeginSubXact(void); extern void AfterTriggerEndSubXact(bool isCommit); - extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); +extern bool AfterTriggerPendingOnRel(Oid relid); /* |