Fix places that were using IsTransactionBlock() as an (inadequate) check
authorTom Lane <[email protected]>
Mon, 21 Oct 2002 22:06:20 +0000 (22:06 +0000)
committerTom Lane <[email protected]>
Mon, 21 Oct 2002 22:06:20 +0000 (22:06 +0000)
that they'd get to commit immediately on finishing.  There's now a
centralized routine PreventTransactionChain() that implements the
necessary tests.

src/backend/access/transam/xact.c
src/backend/catalog/heap.c
src/backend/catalog/index.c
src/backend/commands/dbcommands.c
src/backend/commands/indexcmds.c
src/backend/commands/tablecmds.c
src/backend/commands/vacuum.c
src/backend/parser/analyze.c
src/include/access/xact.h

index e18487cc7c6a56b282b84ee3799faf622cf4410a..2b0e725d9ef45da2c56b186ea292056e115ecc78 100644 (file)
@@ -1280,9 +1280,12 @@ CommitTransactionCommand(bool forceCommit)
                         *
                         * Autocommit mode is forced by either a true forceCommit
                         * parameter to me, or a true preventChain parameter to the
-                        * preceding StartTransactionCommand call.      This is needed so
-                        * that commands like VACUUM can ensure that the right things
-                        * happen.
+                        * preceding StartTransactionCommand call, or a
+                        * PreventTransactionChain call during the transaction.
+                        * (The parameters could be omitted, but it turns out most
+                        * callers of StartTransactionCommand/CommitTransactionCommand
+                        * want to force autocommit, so making them all call
+                        * PreventTransactionChain would just be extra notation.)
                         */
                case TBLOCK_DEFAULT:
                        if (autocommit || forceCommit || suppressChain)
@@ -1429,6 +1432,60 @@ AbortCurrentTransaction(void)
        }
 }
 
+/* --------------------------------
+ *             PreventTransactionChain
+ *
+ * This routine is to be called by statements that must not run inside
+ * a transaction block, typically because they have non-rollback-able
+ * side effects or do internal commits.
+ *
+ * If we have already started a transaction block, issue an error; also issue
+ * an error if we appear to be running inside a user-defined function (which
+ * could issue more commands and possibly cause a failure after the statement
+ * completes).  In autocommit-off mode, we allow the statement if a block is
+ * not already started, and force the statement to be autocommitted despite
+ * the mode.
+ *
+ * stmtNode: pointer to parameter block for statement; this is used in
+ * a very klugy way to determine whether we are inside a function.
+ * stmtType: statement type name for error messages.
+ * --------------------------------
+ */
+void
+PreventTransactionChain(void *stmtNode, const char *stmtType)
+{
+       /*
+        * xact block already started?
+        */
+       if (IsTransactionBlock())
+       {
+               /* translator: %s represents an SQL statement name */
+               elog(ERROR, "%s cannot run inside a transaction block", stmtType);
+       }
+       /*
+        * Are we inside a function call?  If the statement's parameter block
+        * was allocated in QueryContext, assume it is an interactive command.
+        * Otherwise assume it is coming from a function.
+        */
+       if (!MemoryContextContains(QueryContext, stmtNode))
+       {
+               /* translator: %s represents an SQL statement name */
+               elog(ERROR, "%s cannot be executed from a function", stmtType);
+       }
+       /* If we got past IsTransactionBlock test, should be in default state */
+       if (CurrentTransactionState->blockState != TBLOCK_DEFAULT)
+               elog(ERROR, "PreventTransactionChain: can't prevent chain");
+       /* okay to set the flag */
+       suppressChain = true;
+       /* If we're in autocommit-off node, generate a notice */
+       if (!autocommit)
+       {
+               /* translator: %s represents an SQL statement name */
+               elog(NOTICE, "%s will be committed automatically", stmtType);
+       }
+}
+
+
 /* ----------------------------------------------------------------
  *                                        transaction block support
  * ----------------------------------------------------------------
index 9481fe83b64f8f86077a688f3716f36f2da0bb5e..0af51365a425c88b5ae373c71c36a26ed3b42abc 100644 (file)
@@ -1917,19 +1917,8 @@ heap_truncate(Oid rid)
        Relation        rel;
 
        /* Open relation for processing, and grab exclusive access on it. */
-
        rel = heap_open(rid, AccessExclusiveLock);
 
-       /*
-        * TRUNCATE TABLE within a transaction block is dangerous, because if
-        * the transaction is later rolled back we have no way to undo
-        * truncation of the relation's physical file.  Disallow it except for
-        * a rel created in the current xact (which would be deleted on abort,
-        * anyway).
-        */
-       if (IsTransactionBlock() && !rel->rd_isnew)
-               elog(ERROR, "TRUNCATE TABLE cannot run inside a transaction block");
-
        /*
         * Release any buffers associated with this relation.  If they're
         * dirty, they're just dropped without bothering to flush to disk.
index 742beebe686a7e31a6f00e44556a287b5bd8a216..be05d894d44863e044579797df4864721a328090 100644 (file)
@@ -1740,16 +1740,6 @@ reindex_index(Oid indexId, bool force, bool inplace)
        Oid                     heapId;
        bool            old;
 
-       /*
-        * REINDEX within a transaction block is dangerous, because if the
-        * transaction is later rolled back we have no way to undo truncation
-        * of the index's physical file.  Disallow it.
-        *
-        * XXX if we're not doing an inplace rebuild, wouldn't this be okay?
-        */
-       if (IsTransactionBlock())
-               elog(ERROR, "REINDEX cannot run inside a transaction block");
-
        /*
         * Open our index relation and get an exclusive lock on it.
         *
index 105e36013773e80f5d401d165673ff7bb8d0e917..5b37ba1e4ca0dc6b86f6c2c362670821409331f9 100644 (file)
@@ -152,8 +152,7 @@ createdb(const CreatedbStmt *stmt)
        }
 
        /* don't call this in a transaction block */
-       if (IsTransactionBlock())
-               elog(ERROR, "CREATE DATABASE: may not be called in a transaction block");
+       PreventTransactionChain((void *) stmt, "CREATE DATABASE");
 
        /*
         * Check for db name conflict.  There is a race condition here, since
@@ -382,8 +381,7 @@ dropdb(const char *dbname)
        if (strcmp(dbname, DatabaseName) == 0)
                elog(ERROR, "DROP DATABASE: cannot be executed on the currently open database");
 
-       if (IsTransactionBlock())
-               elog(ERROR, "DROP DATABASE: may not be called in a transaction block");
+       PreventTransactionChain((void *) dbname, "DROP DATABASE");
 
        /*
         * Obtain exclusive lock on pg_database.  We need this to ensure that
index 7ca6915933f48c23eb4dc5d46a755ad24355e5d3..dd94ad042702893e2e01689132f025e8c879f2bb 100644 (file)
@@ -560,15 +560,10 @@ ReindexIndex(RangeVar *indexRelation, bool force /* currently unused */ )
 {
        Oid                     indOid;
        HeapTuple       tuple;
-       bool            overwrite = false;
+       bool            overwrite;
 
-       /*
-        * REINDEX within a transaction block is dangerous, because if the
-        * transaction is later rolled back we have no way to undo truncation
-        * of the index's physical file.  Disallow it.
-        */
-       if (IsTransactionBlock())
-               elog(ERROR, "REINDEX cannot run inside a BEGIN/END block");
+       /* Choose in-place-or-not mode */
+       overwrite = IsIgnoringSystemIndexes();
 
        indOid = RangeVarGetRelid(indexRelation, false);
        tuple = SearchSysCache(RELOID,
@@ -595,8 +590,14 @@ ReindexIndex(RangeVar *indexRelation, bool force /* currently unused */ )
 
        ReleaseSysCache(tuple);
 
-       if (IsIgnoringSystemIndexes())
-               overwrite = true;
+       /*
+        * In-place REINDEX within a transaction block is dangerous, because
+        * if the transaction is later rolled back we have no way to undo
+        * truncation of the index's physical file.  Disallow it.
+        */
+       if (overwrite)
+               PreventTransactionChain((void *) indexRelation, "REINDEX");
+
        if (!reindex_index(indOid, force, overwrite))
                elog(WARNING, "index \"%s\" wasn't reindexed", indexRelation->relname);
 }
@@ -611,14 +612,6 @@ ReindexTable(RangeVar *relation, bool force)
        Oid                     heapOid;
        char            relkind;
 
-       /*
-        * REINDEX within a transaction block is dangerous, because if the
-        * transaction is later rolled back we have no way to undo truncation
-        * of the index's physical file.  Disallow it.
-        */
-       if (IsTransactionBlock())
-               elog(ERROR, "REINDEX cannot run inside a BEGIN/END block");
-
        heapOid = RangeVarGetRelid(relation, false);
        relkind = get_rel_relkind(heapOid);
 
@@ -626,6 +619,17 @@ ReindexTable(RangeVar *relation, bool force)
                elog(ERROR, "relation \"%s\" is of type \"%c\"",
                         relation->relname, relkind);
 
+       /*
+        * In-place REINDEX within a transaction block is dangerous, because
+        * if the transaction is later rolled back we have no way to undo
+        * truncation of the index's physical file.  Disallow it.
+        *
+        * XXX we assume that in-place reindex will only be done if
+        * IsIgnoringSystemIndexes() is true.
+        */
+       if (IsIgnoringSystemIndexes())
+               PreventTransactionChain((void *) relation, "REINDEX");
+
        if (!reindex_relation(heapOid, force))
                elog(WARNING, "table \"%s\" wasn't reindexed", relation->relname);
 }
@@ -666,12 +670,7 @@ ReindexDatabase(const char *dbname, bool force, bool all)
         * transaction, then our commit- and start-transaction-command calls
         * would not have the intended effect!
         */
-       if (IsTransactionBlock())
-               elog(ERROR, "REINDEX DATABASE cannot run inside a BEGIN/END block");
-
-       /* Running this from a function would free the function context */
-       if (!MemoryContextContains(QueryContext, (void *) dbname))
-               elog(ERROR, "REINDEX DATABASE cannot be executed from a function");
+       PreventTransactionChain((void *) dbname, "REINDEX");
 
        /*
         * Create a memory context that will survive forced transaction
index c6b82b58294e19683c95a17b87ebf317a5227765..b9fc715c44caebb59254c9bb59cdf12f74e21777 100644 (file)
@@ -375,6 +375,16 @@ TruncateRelation(const RangeVar *relation)
        if (!pg_class_ownercheck(relid, GetUserId()))
                aclcheck_error(ACLCHECK_NOT_OWNER, RelationGetRelationName(rel));
 
+       /*
+        * Truncate within a transaction block is dangerous, because if
+        * the transaction is later rolled back we have no way to undo
+        * truncation of the relation's physical file.  Disallow it except for
+        * a rel created in the current xact (which would be deleted on abort,
+        * anyway).
+        */
+       if (!rel->rd_isnew)
+               PreventTransactionChain((void *) relation, "TRUNCATE TABLE");
+
        /*
         * Don't allow truncate on temp tables of other backends ... their
         * local buffer manager is not going to cope.
index 8a10d89b707decb50ec394b57015e76535cac98d..01456f7706dd4edd12b445a44342d5baa72db4df 100644 (file)
@@ -177,12 +177,8 @@ vacuum(VacuumStmt *vacstmt)
         * user's transaction too, which would certainly not be the desired
         * behavior.
         */
-       if (vacstmt->vacuum && IsTransactionBlock())
-               elog(ERROR, "%s cannot run inside a BEGIN/END block", stmttype);
-
-       /* Running VACUUM from a function would free the function context */
-       if (vacstmt->vacuum && !MemoryContextContains(QueryContext, vacstmt))
-               elog(ERROR, "%s cannot be executed from a function", stmttype);
+       if (vacstmt->vacuum)
+               PreventTransactionChain((void *) vacstmt, stmttype);
 
        /*
         * Send info about dead objects to the statistics collector
index a330bf4d239524e1e051fb7f82a8755df1ced16c..e07f9dd6948b35340cfd7b59f58dd7e45049449c 100644 (file)
@@ -1631,16 +1631,6 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
                if (stmt->forUpdate)
                        elog(ERROR, "DECLARE/UPDATE is not supported"
                                 "\n\tCursors must be READ ONLY");
-
-               /*
-                * 15 august 1991 -- since 3.0 postgres does locking right, we
-                * discovered that portals were violating locking protocol. portal
-                * locks cannot span xacts. as a short-term fix, we installed the
-                * check here. -- mao
-                */
-               if (!IsTransactionBlock())
-                       elog(ERROR, "DECLARE CURSOR may only be used in begin/end transaction blocks");
-
                qry->into = makeNode(RangeVar);
                qry->into->relname = stmt->portalname;
                qry->isPortal = TRUE;
@@ -1849,16 +1839,6 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
                if (forUpdate)
                        elog(ERROR, "DECLARE/UPDATE is not supported"
                                 "\n\tCursors must be READ ONLY");
-
-               /*
-                * 15 august 1991 -- since 3.0 postgres does locking right, we
-                * discovered that portals were violating locking protocol. portal
-                * locks cannot span xacts. as a short-term fix, we installed the
-                * check here. -- mao
-                */
-               if (!IsTransactionBlock())
-                       elog(ERROR, "DECLARE CURSOR may only be used in begin/end transaction blocks");
-
                qry->into = makeNode(RangeVar);
                qry->into->relname = portalname;
                qry->isPortal = TRUE;
index 6c40665092ff765a5d93fac79d10e5e7a72106aa..4fca596a6d28e0a4bacb1b5c33c66ee86379236f 100644 (file)
@@ -113,6 +113,7 @@ extern void EndTransactionBlock(void);
 extern bool IsTransactionBlock(void);
 extern void UserAbortTransactionBlock(void);
 extern void AbortOutOfAnyTransaction(void);
+extern void PreventTransactionChain(void *stmtNode, const char *stmtType);
 
 extern void RecordTransactionCommit(void);