* Copyright (c) 2000-2010, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.89 2010/02/17 03:10:33 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.90 2010/02/20 21:24:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
                return BootstrapTransactionId;
        }
 
+       /* safety check, we should never get this far in a HS slave */
+       if (RecoveryInProgress())
+               elog(ERROR, "cannot assign TransactionIds during recovery");
+
        LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
 
        xid = ShmemVariableCache->nextXid;
 {
        Oid                     result;
 
+       /* safety check, we should never get this far in a HS slave */
+       if (RecoveryInProgress())
+               elog(ERROR, "cannot assign OIDs during recovery");
+
        LWLockAcquire(OidGenLock, LW_EXCLUSIVE);
 
        /*
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.287 2010/02/17 04:19:39 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.288 2010/02/20 21:24:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
        bool            isSubXact = (s->parent != NULL);
        ResourceOwner currentOwner;
 
-       if (RecoveryInProgress())
-               elog(ERROR, "cannot assign TransactionIds during recovery");
-
        /* Assert that caller didn't screw up */
        Assert(!TransactionIdIsValid(s->transactionId));
        Assert(s->state == TRANS_INPROGRESS);
 
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.123 2010/02/14 18:42:13 rhaas Exp $
+ *       $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.124 2010/02/20 21:24:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
                                 errmsg("permission denied to create temporary tables in database \"%s\"",
                                                get_database_name(MyDatabaseId))));
 
+       /*
+        * Do not allow a Hot Standby slave session to make temp tables.  Aside
+        * from problems with modifying the system catalogs, there is a naming
+        * conflict: pg_temp_N belongs to the session with BackendId N on the
+        * master, not to a slave session with the same BackendId.  We should
+        * not be able to get here anyway due to XactReadOnly checks, but let's
+        * just make real sure.  Note that this also backstops various operations
+        * that allow XactReadOnly transactions to modify temp tables; they'd need
+        * RecoveryInProgress checks if not for this.
+        */
+       if (RecoveryInProgress())
+               ereport(ERROR,
+                               (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
+                                errmsg("cannot create temporary tables during recovery")));
+
        snprintf(namespaceName, sizeof(namespaceName), "pg_temp_%d", MyBackendId);
 
        namespaceId = GetSysCacheOid1(NAMESPACENAME,
 
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/commands/async.c,v 1.153 2010/02/17 16:54:06 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/commands/async.c,v 1.154 2010/02/20 21:24:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
        else
                payload = text_to_cstring(PG_GETARG_TEXT_PP(1));
 
+       /* For NOTIFY as a statement, this is checked in ProcessUtility */
+       PreventCommandDuringRecovery("NOTIFY");
+
        Async_Notify(channel, payload);
 
        PG_RETURN_VOID();
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.324 2010/02/08 04:33:53 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.325 2010/02/20 21:24:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
                /* check read-only transaction */
                if (XactReadOnly && is_from && !cstate->rel->rd_islocaltemp)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
-                                        errmsg("transaction is read-only")));
+                       PreventCommandIfReadOnly("COPY FROM");
 
                /* Don't allow COPY w/ OIDs to or from a table without them */
                if (cstate->oids && !cstate->rel->rd_rel->relhasoids)
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/commands/lockcmds.c,v 1.27 2010/01/02 16:57:37 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/commands/lockcmds.c,v 1.28 2010/02/20 21:24:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
                 * This test must match the restrictions defined in LockAcquire()
                 */
                if (lockstmt->mode > RowExclusiveLock)
-                       PreventCommandDuringRecovery();
+                       PreventCommandDuringRecovery("LOCK TABLE");
 
                LockTableRecurse(reloid, relation,
                                                 lockstmt->mode, lockstmt->nowait, recurse);
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/commands/sequence.c,v 1.167 2010/02/19 06:29:19 heikki Exp $
+ *       $PostgreSQL: pgsql/src/backend/commands/sequence.c,v 1.168 2010/02/20 21:24:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
                                rescnt = 0;
        bool            logit = false;
 
-       /* nextval() writes to database and must be prevented during recovery */
-       PreventCommandDuringRecovery();
-
        /* open and AccessShareLock sequence */
        init_sequence(relid, &elm, &seqrel);
 
                                 errmsg("permission denied for sequence %s",
                                                RelationGetRelationName(seqrel))));
 
+       /* read-only transactions may only modify temp sequences */
+       if (!seqrel->rd_islocaltemp)
+               PreventCommandIfReadOnly("nextval()");
+
        if (elm->last != elm->cached)           /* some numbers were cached */
        {
                Assert(elm->last_valid);
        Buffer          buf;
        Form_pg_sequence seq;
 
-       /* setval() writes to database and must be prevented during recovery */
-       PreventCommandDuringRecovery();
-
        /* open and AccessShareLock sequence */
        init_sequence(relid, &elm, &seqrel);
 
                                 errmsg("permission denied for sequence %s",
                                                RelationGetRelationName(seqrel))));
 
+       /* read-only transactions may only modify temp sequences */
+       if (!seqrel->rd_islocaltemp)
+               PreventCommandIfReadOnly("setval()");
+
        /* lock page' buffer and read tuple */
        seq = read_info(elm, seqrel, &buf);
 
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.346 2010/02/09 21:43:30 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.347 2010/02/20 21:24:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
+#include "tcop/utility.h"
 #include "utils/acl.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 
 /*
  * Check that the query does not imply any writes to non-temp tables.
+ *
+ * Note: in a Hot Standby slave this would need to reject writes to temp
+ * tables as well; but an HS slave can't have created any temp tables
+ * in the first place, so no need to check that.
  */
 static void
 ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
        /*
         * CREATE TABLE AS or SELECT INTO?
         *
-        * XXX should we allow this if the destination is temp?
+        * XXX should we allow this if the destination is temp?  Considering
+        * that it would still require catalog changes, probably not.
         */
        if (plannedstmt->intoClause != NULL)
-               goto fail;
+               PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
 
        /* Fail if write permissions are requested on any non-temp table */
        foreach(l, plannedstmt->rtable)
                if (isTempNamespace(get_rel_namespace(rte->relid)))
                        continue;
 
-               goto fail;
+               PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
        }
-
-       return;
-
-fail:
-       ereport(ERROR,
-                       (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
-                        errmsg("transaction is read-only")));
 }
 
 
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.333 2010/02/16 22:34:50 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.334 2010/02/20 21:24:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
        /*
         * Note: Commands that need to do more complicated checking are handled
         * elsewhere, in particular COPY and plannable statements do their own
-        * checking.
+        * checking.  However they should all call PreventCommandIfReadOnly to
+        * actually throw the error.
         */
 
        switch (nodeTag(parsetree))
                case T_AlterUserMappingStmt:
                case T_DropUserMappingStmt:
                case T_AlterTableSpaceOptionsStmt:
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
-                                        errmsg("transaction is read-only")));
+                       PreventCommandIfReadOnly(CreateCommandTag(parsetree));
                        break;
                default:
                        /* do nothing */
        }
 }
 
+/*
+ * PreventCommandIfReadOnly: throw error if XactReadOnly
+ *
+ * This is useful mainly to ensure consistency of the error message wording;
+ * most callers have checked XactReadOnly for themselves.
+ */
+void
+PreventCommandIfReadOnly(const char *cmdname)
+{
+       if (XactReadOnly)
+               ereport(ERROR,
+                               (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
+                                /* translator: %s is name of a SQL command, eg CREATE */
+                                errmsg("cannot execute %s in a read-only transaction",
+                                               cmdname)));
+}
+
+/*
+ * PreventCommandDuringRecovery: throw error if RecoveryInProgress
+ *
+ * The majority of operations that are unsafe in a Hot Standby slave
+ * will be rejected by XactReadOnly tests.  However there are a few
+ * commands that are allowed in "read-only" xacts but cannot be allowed
+ * in Hot Standby mode.  Those commands should call this function.
+ */
+void
+PreventCommandDuringRecovery(const char *cmdname)
+{
+       if (RecoveryInProgress())
+               ereport(ERROR,
+                               (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
+                                /* translator: %s is name of a SQL command, eg CREATE */
+                                errmsg("cannot execute %s during recovery",
+                                               cmdname)));
+}
 
 /*
  * CheckRestrictedOperation: throw error for hazardous command if we're
                                                break;
 
                                        case TRANS_STMT_PREPARE:
-                                               PreventCommandDuringRecovery();
+                                               PreventCommandDuringRecovery("PREPARE TRANSACTION");
                                                if (!PrepareTransactionBlock(stmt->gid))
                                                {
                                                        /* report unsuccessful commit in completionTag */
                                                break;
 
                                        case TRANS_STMT_COMMIT_PREPARED:
-                                               PreventCommandDuringRecovery();
                                                PreventTransactionChain(isTopLevel, "COMMIT PREPARED");
+                                               PreventCommandDuringRecovery("COMMIT PREPARED");
                                                FinishPreparedTransaction(stmt->gid, true);
                                                break;
 
                                        case TRANS_STMT_ROLLBACK_PREPARED:
-                                               PreventCommandDuringRecovery();
                                                PreventTransactionChain(isTopLevel, "ROLLBACK PREPARED");
+                                               PreventCommandDuringRecovery("ROLLBACK PREPARED");
                                                FinishPreparedTransaction(stmt->gid, false);
                                                break;
 
                        break;
 
                case T_GrantStmt:
-                       PreventCommandDuringRecovery();
                        ExecuteGrantStmt((GrantStmt *) parsetree);
                        break;
 
                        {
                                NotifyStmt *stmt = (NotifyStmt *) parsetree;
 
-                               PreventCommandDuringRecovery();
+                               PreventCommandDuringRecovery("NOTIFY");
                                Async_Notify(stmt->conditionname, stmt->payload);
                        }
                        break;
                        {
                                ListenStmt *stmt = (ListenStmt *) parsetree;
 
-                               PreventCommandDuringRecovery();
+                               PreventCommandDuringRecovery("LISTEN");
                                CheckRestrictedOperation("LISTEN");
                                Async_Listen(stmt->conditionname);
                        }
                        {
                                UnlistenStmt *stmt = (UnlistenStmt *) parsetree;
 
-                               PreventCommandDuringRecovery();
+                               PreventCommandDuringRecovery("UNLISTEN");
                                CheckRestrictedOperation("UNLISTEN");
                                if (stmt->conditionname)
                                        Async_Unlisten(stmt->conditionname);
                        break;
 
                case T_ClusterStmt:
-                       PreventCommandDuringRecovery();
+                       /* we choose to allow this during "read only" transactions */
+                       PreventCommandDuringRecovery("CLUSTER");
                        cluster((ClusterStmt *) parsetree, isTopLevel);
                        break;
 
                case T_VacuumStmt:
-                       PreventCommandDuringRecovery();
+                       /* we choose to allow this during "read only" transactions */
+                       PreventCommandDuringRecovery("VACUUM");
                        vacuum((VacuumStmt *) parsetree, InvalidOid, true, NULL, false,
                                   isTopLevel);
                        break;
                         * using various forms of replication.
                         */
                        RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT |
-                                                               (RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
+                                                         (RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
                        break;
 
                case T_ReindexStmt:
                        {
                                ReindexStmt *stmt = (ReindexStmt *) parsetree;
 
-                               PreventCommandDuringRecovery();
+                               /* we choose to allow this during "read only" transactions */
+                               PreventCommandDuringRecovery("REINDEX");
                                switch (stmt->kind)
                                {
                                        case OBJECT_INDEX:
 
        return lev;
 }
-
-void
-PreventCommandDuringRecovery(void)
-{
-       if (RecoveryInProgress())
-               ereport(ERROR,
-                       (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
-                        errmsg("cannot be executed during recovery")));
-}
 
  *     Author: Jan Wieck, Afilias USA INC.
  *     64-bit txids: Marko Kreen, Skype Technologies
  *
- *     $PostgreSQL: pgsql/src/backend/utils/adt/txid.c,v 1.11 2010/01/07 04:53:34 tgl Exp $
+ *     $PostgreSQL: pgsql/src/backend/utils/adt/txid.c,v 1.12 2010/02/20 21:24:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
         * return a valid current xid, so we should not change
         * this to return NULL or similar invalid xid.
         */
-       PreventCommandDuringRecovery();
+       PreventCommandDuringRecovery("txid_current()");
 
        load_xid_epoch(&state);
 
 
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/miscadmin.h,v 1.218 2010/02/07 20:48:13 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/miscadmin.h,v 1.219 2010/02/20 21:24:02 tgl Exp $
  *
  * NOTES
  *       some of the information in this file should be moved to other files.
 extern void check_stack_depth(void);
 
 /* in tcop/utility.c */
-extern void PreventCommandDuringRecovery(void);
+extern void PreventCommandIfReadOnly(const char *cmdname);
+extern void PreventCommandDuringRecovery(const char *cmdname);
 
 /* in utils/misc/guc.c */
 extern int trace_recovery_messages;
 
 CREATE TEMPORARY TABLE temptest (a int);
 SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;
 DROP TABLE writetest; -- fail
-ERROR:  transaction is read-only
+ERROR:  cannot execute DROP TABLE in a read-only transaction
 INSERT INTO writetest VALUES (1); -- fail
-ERROR:  transaction is read-only
+ERROR:  cannot execute INSERT in a read-only transaction
 SELECT * FROM writetest; -- ok
  a 
 ---
 UPDATE temptest SET a = 0 FROM writetest WHERE temptest.a = 1 AND writetest.a = temptest.a; -- ok
 PREPARE test AS UPDATE writetest SET a = 0; -- ok
 EXECUTE test; -- fail
-ERROR:  transaction is read-only
+ERROR:  cannot execute UPDATE in a read-only transaction
 SELECT * FROM writetest, temptest; -- ok
  a | a 
 ---+---
 (0 rows)
 
 CREATE TABLE test AS SELECT * FROM writetest; -- fail
-ERROR:  transaction is read-only
+ERROR:  cannot execute SELECT INTO in a read-only transaction
 START TRANSACTION READ WRITE;
 DROP TABLE writetest; -- ok
 COMMIT;