diff options
-rw-r--r-- | src/backend/catalog/index.c | 19 | ||||
-rw-r--r-- | src/backend/commands/cluster.c | 9 | ||||
-rw-r--r-- | src/backend/commands/tablecmds.c | 108 | ||||
-rw-r--r-- | src/backend/commands/trigger.c | 43 | ||||
-rw-r--r-- | src/include/commands/tablecmds.h | 4 | ||||
-rw-r--r-- | src/include/commands/trigger.h | 4 |
6 files changed, 113 insertions, 74 deletions
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index dcc4aa0a98c..34a5b97f045 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.274.2.2 2008/01/03 21:23:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.274.2.3 2008/05/27 21:13:25 tgl Exp $ * * * INTERFACE ROUTINES @@ -33,10 +33,12 @@ #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/indexing.h" +#include "catalog/namespace.h" #include "catalog/pg_constraint.h" #include "catalog/pg_operator.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" @@ -1955,6 +1957,21 @@ reindex_index(Oid indexId) iRel = index_open(indexId, 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 f0825a9d02f..9164641b748 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.154.2.2 2007/09/29 18:05:28 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.154.2.3 2008/05/27 21:13:25 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -28,6 +28,7 @@ #include "catalog/namespace.h" #include "catalog/toasting.h" #include "commands/cluster.h" +#include "commands/tablecmds.h" #include "miscadmin.h" #include "utils/acl.h" #include "utils/fmgroids.h" @@ -449,6 +450,12 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck) (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, NoLock); } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index a9a13a803e5..3006f0a3627 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.206.2.5 2008/05/09 22:37:41 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.206.2.6 2008/05/27 21:13:25 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -597,13 +597,6 @@ ExecuteTruncate(TruncateStmt *stmt) #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. */ foreach(cell, rels) @@ -683,6 +676,12 @@ truncate_check_rel(Relation rel) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 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"); } /*---------- @@ -1913,6 +1912,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 * @@ -1949,26 +1997,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)); } @@ -1981,7 +2011,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) @@ -2939,12 +2970,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); } @@ -2976,12 +3002,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); } @@ -3799,12 +3820,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 47de3ca6fc2..75e0cf150d8 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.210.2.4 2007/08/15 19:15:55 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.210.2.5 2008/05/27 21:13:25 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -3190,28 +3190,29 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt) } /* ---------- - * 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; @@ -3221,21 +3222,18 @@ AfterTriggerCheckTruncate(List *relids) /* * 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++) @@ -3247,13 +3245,12 @@ AfterTriggerCheckTruncate(List *relids) 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; } diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index f50a21577ae..f2e2b277896 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/tablecmds.h,v 1.31.2.1 2007/05/11 20:18:17 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/tablecmds.h,v 1.31.2.2 2008/05/27 21:13:25 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -34,6 +34,8 @@ extern void AlterRelationNamespaceInternal(Relation classRel, Oid relOid, Oid oldNspOid, Oid newNspOid, bool hasDependEntry); +extern void CheckTableNotInUse(Relation rel, const char *stmt); + extern void ExecuteTruncate(TruncateStmt *stmt); extern void renameatt(Oid myrelid, diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 31253d8b4fe..d88f5848854 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.59 2006/09/04 21:15:56 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.59.2.1 2008/05/27 21:13:25 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -165,7 +165,7 @@ extern void AfterTriggerEndXact(bool isCommit); extern void AfterTriggerBeginSubXact(void); extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); -extern void AfterTriggerCheckTruncate(List *relids); +extern bool AfterTriggerPendingOnRel(Oid relid); /* |