aboutsummaryrefslogtreecommitdiff
path: root/src/backend/tcop/utility.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/tcop/utility.c')
-rw-r--r--src/backend/tcop/utility.c350
1 files changed, 250 insertions, 100 deletions
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index a50e7ff4b43..de84dd27a14 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -75,6 +75,7 @@
ProcessUtility_hook_type ProcessUtility_hook = NULL;
/* local function declarations */
+static int ClassifyUtilityCommandAsReadOnly(Node *parsetree);
static void ProcessUtilitySlow(ParseState *pstate,
PlannedStmt *pstmt,
const char *queryString,
@@ -124,106 +125,269 @@ CommandIsReadOnly(PlannedStmt *pstmt)
}
/*
- * check_xact_readonly: is a utility command read-only?
+ * Determine the degree to which a utility command is read only.
*
- * Here we use the loose rules of XactReadOnly mode: no permanent effects
- * on the database are allowed.
+ * Note the definitions of the relevant flags in src/include/utility/tcop.h.
*/
-static void
-check_xact_readonly(Node *parsetree)
+int
+ClassifyUtilityCommandAsReadOnly(Node *parsetree)
{
- /* Only perform the check if we have a reason to do so. */
- if (!XactReadOnly && !IsInParallelMode())
- return;
-
- /*
- * Note: Commands that need to do more complicated checking are handled
- * elsewhere, in particular COPY and plannable statements do their own
- * checking. However they should all call PreventCommandIfReadOnly or
- * PreventCommandIfParallelMode to actually throw the error.
- */
-
switch (nodeTag(parsetree))
{
- case T_AlterDatabaseStmt:
+ case T_AlterCollationStmt:
case T_AlterDatabaseSetStmt:
+ case T_AlterDatabaseStmt:
+ case T_AlterDefaultPrivilegesStmt:
case T_AlterDomainStmt:
+ case T_AlterEnumStmt:
+ case T_AlterEventTrigStmt:
+ case T_AlterExtensionContentsStmt:
+ case T_AlterExtensionStmt:
+ case T_AlterFdwStmt:
+ case T_AlterForeignServerStmt:
case T_AlterFunctionStmt:
- case T_AlterRoleStmt:
- case T_AlterRoleSetStmt:
case T_AlterObjectDependsStmt:
case T_AlterObjectSchemaStmt:
- case T_AlterOwnerStmt:
+ case T_AlterOpFamilyStmt:
case T_AlterOperatorStmt:
+ case T_AlterOwnerStmt:
+ case T_AlterPolicyStmt:
+ case T_AlterPublicationStmt:
+ case T_AlterRoleSetStmt:
+ case T_AlterRoleStmt:
case T_AlterSeqStmt:
+ case T_AlterStatsStmt:
+ case T_AlterSubscriptionStmt:
+ case T_AlterTSConfigurationStmt:
+ case T_AlterTSDictionaryStmt:
case T_AlterTableMoveAllStmt:
+ case T_AlterTableSpaceOptionsStmt:
case T_AlterTableStmt:
- case T_RenameStmt:
+ case T_AlterUserMappingStmt:
case T_CommentStmt:
- case T_DefineStmt:
+ case T_CompositeTypeStmt:
+ case T_CreateAmStmt:
case T_CreateCastStmt:
- case T_CreateEventTrigStmt:
- case T_AlterEventTrigStmt:
case T_CreateConversionStmt:
- case T_CreatedbStmt:
case T_CreateDomainStmt:
+ case T_CreateEnumStmt:
+ case T_CreateEventTrigStmt:
+ case T_CreateExtensionStmt:
+ case T_CreateFdwStmt:
+ case T_CreateForeignServerStmt:
+ case T_CreateForeignTableStmt:
case T_CreateFunctionStmt:
- case T_CreateRoleStmt:
- case T_IndexStmt:
- case T_CreatePLangStmt:
case T_CreateOpClassStmt:
case T_CreateOpFamilyStmt:
- case T_AlterOpFamilyStmt:
- case T_RuleStmt:
+ case T_CreatePLangStmt:
+ case T_CreatePolicyStmt:
+ case T_CreatePublicationStmt:
+ case T_CreateRangeStmt:
+ case T_CreateRoleStmt:
case T_CreateSchemaStmt:
case T_CreateSeqStmt:
+ case T_CreateStatsStmt:
case T_CreateStmt:
+ case T_CreateSubscriptionStmt:
case T_CreateTableAsStmt:
- case T_RefreshMatViewStmt:
case T_CreateTableSpaceStmt:
case T_CreateTransformStmt:
case T_CreateTrigStmt:
- case T_CompositeTypeStmt:
- case T_CreateEnumStmt:
- case T_CreateRangeStmt:
- case T_AlterEnumStmt:
- case T_ViewStmt:
+ case T_CreateUserMappingStmt:
+ case T_CreatedbStmt:
+ case T_DefineStmt:
+ case T_DropOwnedStmt:
+ case T_DropRoleStmt:
case T_DropStmt:
- case T_DropdbStmt:
+ case T_DropSubscriptionStmt:
case T_DropTableSpaceStmt:
- case T_DropRoleStmt:
- case T_GrantStmt:
- case T_GrantRoleStmt:
- case T_AlterDefaultPrivilegesStmt:
- case T_TruncateStmt:
- case T_DropOwnedStmt:
- case T_ReassignOwnedStmt:
- case T_AlterTSDictionaryStmt:
- case T_AlterTSConfigurationStmt:
- case T_CreateExtensionStmt:
- case T_AlterExtensionStmt:
- case T_AlterExtensionContentsStmt:
- case T_CreateFdwStmt:
- case T_AlterFdwStmt:
- case T_CreateForeignServerStmt:
- case T_AlterForeignServerStmt:
- case T_CreateUserMappingStmt:
- case T_AlterUserMappingStmt:
case T_DropUserMappingStmt:
- case T_AlterTableSpaceOptionsStmt:
- case T_CreateForeignTableStmt:
+ case T_DropdbStmt:
+ case T_GrantRoleStmt:
+ case T_GrantStmt:
case T_ImportForeignSchemaStmt:
+ case T_IndexStmt:
+ case T_ReassignOwnedStmt:
+ case T_RefreshMatViewStmt:
+ case T_RenameStmt:
+ case T_RuleStmt:
case T_SecLabelStmt:
- case T_CreatePublicationStmt:
- case T_AlterPublicationStmt:
- case T_CreateSubscriptionStmt:
- case T_AlterSubscriptionStmt:
- case T_DropSubscriptionStmt:
- PreventCommandIfReadOnly(CreateCommandTag(parsetree));
- PreventCommandIfParallelMode(CreateCommandTag(parsetree));
- break;
+ case T_TruncateStmt:
+ case T_ViewStmt:
+ {
+ /* DDL is not read-only, and neither is TRUNCATE. */
+ return COMMAND_IS_NOT_READ_ONLY;
+ }
+
+ case T_AlterSystemStmt:
+ {
+ /*
+ * Surprisingly, ALTER SYSTEM meets all our definitions of
+ * read-only: it changes nothing that affects the output of
+ * pg_dump, it doesn't write WAL or imperil the application
+ * of future WAL, and it doesn't depend on any state that needs
+ * to be synchronized with parallel workers.
+ *
+ * So, despite the fact that it writes to a file, it's read
+ * only!
+ */
+ return COMMAND_IS_STRICTLY_READ_ONLY;
+ }
+
+ case T_CallStmt:
+ case T_DoStmt:
+ {
+ /*
+ * Commands inside the DO block or the called procedure might
+ * not be read only, but they'll be checked separately when we
+ * try to execute them. Here we only need to worry about the
+ * DO or CALL command itself.
+ */
+ return COMMAND_IS_STRICTLY_READ_ONLY;
+ }
+
+ case T_CheckPointStmt:
+ {
+ /*
+ * You might think that this should not be permitted in
+ * recovery, but we interpret a CHECKPOINT command during
+ * recovery as a request for a restartpoint instead. We allow
+ * this since it can be a useful way of reducing switchover
+ * time when using various forms of replication.
+ */
+ return COMMAND_IS_STRICTLY_READ_ONLY;
+ }
+
+ case T_ClosePortalStmt:
+ case T_ConstraintsSetStmt:
+ case T_DeallocateStmt:
+ case T_DeclareCursorStmt:
+ case T_DiscardStmt:
+ case T_ExecuteStmt:
+ case T_FetchStmt:
+ case T_LoadStmt:
+ case T_PrepareStmt:
+ case T_UnlistenStmt:
+ case T_VariableSetStmt:
+ {
+ /*
+ * These modify only backend-local state, so they're OK to
+ * run in a read-only transaction or on a standby. However,
+ * they are disallowed in parallel mode, because they either
+ * rely upon or modify backend-local state that might not be
+ * synchronized among cooperating backends.
+ */
+ return COMMAND_OK_IN_RECOVERY | COMMAND_OK_IN_READ_ONLY_TXN;
+ }
+
+ case T_ClusterStmt:
+ case T_ReindexStmt:
+ case T_VacuumStmt:
+ {
+ /*
+ * These commands write WAL, so they're not strictly read-only,
+ * and running them in parallel workers isn't supported.
+ *
+ * However, they don't change the database state in a way that
+ * would affect pg_dump output, so it's fine to run them in a
+ * read-only transaction. (CLUSTER might change the order of
+ * rows on disk, which could affect the ordering of pg_dump
+ * output, but that's not semantically significant.)
+ */
+ return COMMAND_OK_IN_READ_ONLY_TXN;
+ }
+
+ case T_CopyStmt:
+ {
+ CopyStmt *stmt = (CopyStmt *) parsetree;
+
+ /*
+ * You might think that COPY FROM is not at all read only,
+ * but it's OK to copy into a temporary table, because that
+ * wouldn't change the output of pg_dump. If the target table
+ * turns out to be non-temporary, DoCopy itself will call
+ * PreventCommandIfReadOnly.
+ */
+ if (stmt->is_from)
+ return COMMAND_OK_IN_READ_ONLY_TXN;
+ else
+ return COMMAND_IS_STRICTLY_READ_ONLY;
+ }
+
+ case T_ExplainStmt:
+ case T_VariableShowStmt:
+ {
+ /*
+ * These commands don't modify any data and are safe to run
+ * in a parallel worker.
+ */
+ return COMMAND_IS_STRICTLY_READ_ONLY;
+ }
+
+ case T_ListenStmt:
+ case T_NotifyStmt:
+ {
+ /*
+ * NOTIFY requires an XID assignment, so it can't be permitted
+ * on a standby. Perhaps LISTEN could, since without NOTIFY
+ * it would be OK to just do nothing, at least until promotion,
+ * but we currently prohibit it lest the user get the wrong
+ * idea.
+ *
+ * (We do allow T_UnlistenStmt on a standby, though, because
+ * it's a no-op.)
+ */
+ return COMMAND_OK_IN_READ_ONLY_TXN;
+ }
+
+ case T_LockStmt:
+ {
+ LockStmt *stmt = (LockStmt *) parsetree;
+
+ /*
+ * Only weaker locker modes are allowed during recovery. The
+ * restrictions here must match those in LockAcquireExtended().
+ */
+ if (stmt->mode > RowExclusiveLock)
+ return COMMAND_OK_IN_READ_ONLY_TXN;
+ else
+ return COMMAND_IS_STRICTLY_READ_ONLY;
+ }
+
+ case T_TransactionStmt:
+ {
+ TransactionStmt *stmt = (TransactionStmt *) parsetree;
+
+ /*
+ * PREPARE, COMMIT PREPARED, and ROLLBACK PREPARED all change
+ * write WAL, so they're not read-only in the strict sense;
+ * but the first and third do not change pg_dump output, so
+ * they're OK in a read-only transactions.
+ *
+ * We also consider COMMIT PREPARED to be OK in a read-only
+ * transaction environment, by way of exception.
+ */
+ switch (stmt->kind)
+ {
+ case TRANS_STMT_BEGIN:
+ case TRANS_STMT_START:
+ case TRANS_STMT_COMMIT:
+ case TRANS_STMT_ROLLBACK:
+ case TRANS_STMT_SAVEPOINT:
+ case TRANS_STMT_RELEASE:
+ case TRANS_STMT_ROLLBACK_TO:
+ return COMMAND_IS_STRICTLY_READ_ONLY;
+
+ case TRANS_STMT_PREPARE:
+ case TRANS_STMT_COMMIT_PREPARED:
+ case TRANS_STMT_ROLLBACK_PREPARED:
+ return COMMAND_OK_IN_READ_ONLY_TXN;
+ }
+ }
+
default:
- /* do nothing */
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(parsetree));
break;
}
}
@@ -231,8 +395,8 @@ check_xact_readonly(Node *parsetree)
/*
* PreventCommandIfReadOnly: throw error if XactReadOnly
*
- * This is useful mainly to ensure consistency of the error message wording;
- * most callers have checked XactReadOnly for themselves.
+ * This is useful partly to ensure consistency of the error message wording;
+ * some callers have checked XactReadOnly for themselves.
*/
void
PreventCommandIfReadOnly(const char *cmdname)
@@ -249,8 +413,8 @@ PreventCommandIfReadOnly(const char *cmdname)
* PreventCommandIfParallelMode: throw error if current (sub)transaction is
* in parallel mode.
*
- * This is useful mainly to ensure consistency of the error message wording;
- * most callers have checked IsInParallelMode() for themselves.
+ * This is useful partly to ensure consistency of the error message wording;
+ * some callers have checked IsInParallelMode() for themselves.
*/
void
PreventCommandIfParallelMode(const char *cmdname)
@@ -385,11 +549,25 @@ standard_ProcessUtility(PlannedStmt *pstmt,
bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
bool isAtomicContext = (!(context == PROCESS_UTILITY_TOPLEVEL || context == PROCESS_UTILITY_QUERY_NONATOMIC) || IsTransactionBlock());
ParseState *pstate;
+ int readonly_flags;
/* This can recurse, so check for excessive recursion */
check_stack_depth();
- check_xact_readonly(parsetree);
+ /* Prohibit read/write commands in read-only states. */
+ readonly_flags = ClassifyUtilityCommandAsReadOnly(parsetree);
+ if (readonly_flags != COMMAND_IS_STRICTLY_READ_ONLY &&
+ (XactReadOnly || IsInParallelMode()))
+ {
+ const char *commandtag = CreateCommandTag(parsetree);
+
+ if ((readonly_flags & COMMAND_OK_IN_READ_ONLY_TXN) == 0)
+ PreventCommandIfReadOnly(commandtag);
+ if ((readonly_flags & COMMAND_OK_IN_PARALLEL_MODE) == 0)
+ PreventCommandIfParallelMode(commandtag);
+ if ((readonly_flags & COMMAND_OK_IN_RECOVERY) == 0)
+ PreventCommandDuringRecovery(commandtag);
+ }
if (completionTag)
completionTag[0] = '\0';
@@ -449,7 +627,6 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case TRANS_STMT_PREPARE:
- PreventCommandDuringRecovery("PREPARE TRANSACTION");
if (!PrepareTransactionBlock(stmt->gid))
{
/* report unsuccessful commit in completionTag */
@@ -460,13 +637,11 @@ standard_ProcessUtility(PlannedStmt *pstmt,
case TRANS_STMT_COMMIT_PREPARED:
PreventInTransactionBlock(isTopLevel, "COMMIT PREPARED");
- PreventCommandDuringRecovery("COMMIT PREPARED");
FinishPreparedTransaction(stmt->gid, true);
break;
case TRANS_STMT_ROLLBACK_PREPARED:
PreventInTransactionBlock(isTopLevel, "ROLLBACK PREPARED");
- PreventCommandDuringRecovery("ROLLBACK PREPARED");
FinishPreparedTransaction(stmt->gid, false);
break;
@@ -607,7 +782,6 @@ standard_ProcessUtility(PlannedStmt *pstmt,
{
NotifyStmt *stmt = (NotifyStmt *) parsetree;
- PreventCommandDuringRecovery("NOTIFY");
Async_Notify(stmt->conditionname, stmt->payload);
}
break;
@@ -616,7 +790,6 @@ standard_ProcessUtility(PlannedStmt *pstmt,
{
ListenStmt *stmt = (ListenStmt *) parsetree;
- PreventCommandDuringRecovery("LISTEN");
CheckRestrictedOperation("LISTEN");
Async_Listen(stmt->conditionname);
}
@@ -626,7 +799,6 @@ standard_ProcessUtility(PlannedStmt *pstmt,
{
UnlistenStmt *stmt = (UnlistenStmt *) parsetree;
- /* we allow UNLISTEN during recovery, as it's a noop */
CheckRestrictedOperation("UNLISTEN");
if (stmt->conditionname)
Async_Unlisten(stmt->conditionname);
@@ -650,22 +822,11 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_ClusterStmt:
- /* we choose to allow this during "read only" transactions */
- PreventCommandDuringRecovery("CLUSTER");
- /* forbidden in parallel mode due to CommandIsReadOnly */
cluster((ClusterStmt *) parsetree, isTopLevel);
break;
case T_VacuumStmt:
- {
- VacuumStmt *stmt = (VacuumStmt *) parsetree;
-
- /* we choose to allow this during "read only" transactions */
- PreventCommandDuringRecovery(stmt->is_vacuumcmd ?
- "VACUUM" : "ANALYZE");
- /* forbidden in parallel mode due to CommandIsReadOnly */
- ExecVacuum(pstate, stmt, isTopLevel);
- }
+ ExecVacuum(pstate, (VacuumStmt *) parsetree, isTopLevel);
break;
case T_ExplainStmt:
@@ -740,7 +901,6 @@ standard_ProcessUtility(PlannedStmt *pstmt,
* outside a transaction block is presumed to be user error.
*/
RequireTransactionBlock(isTopLevel, "LOCK TABLE");
- /* forbidden in parallel mode due to CommandIsReadOnly */
LockTableCommand((LockStmt *) parsetree);
break;
@@ -755,13 +915,6 @@ standard_ProcessUtility(PlannedStmt *pstmt,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to do CHECKPOINT")));
- /*
- * You might think we should have a PreventCommandDuringRecovery()
- * here, but we interpret a CHECKPOINT command during recovery as
- * a request for a restartpoint instead. We allow this since it
- * can be a useful way of reducing switchover time when using
- * various forms of replication.
- */
RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT |
(RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
break;
@@ -774,9 +927,6 @@ standard_ProcessUtility(PlannedStmt *pstmt,
PreventInTransactionBlock(isTopLevel,
"REINDEX CONCURRENTLY");
- /* we choose to allow this during "read only" transactions */
- PreventCommandDuringRecovery("REINDEX");
- /* forbidden in parallel mode due to CommandIsReadOnly */
switch (stmt->kind)
{
case REINDEX_OBJECT_INDEX: