diff options
Diffstat (limited to 'src/backend/tcop/utility.c')
-rw-r--r-- | src/backend/tcop/utility.c | 3741 |
1 files changed, 3741 insertions, 0 deletions
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c new file mode 100644 index 0000000..6fb4d64 --- /dev/null +++ b/src/backend/tcop/utility.c @@ -0,0 +1,3741 @@ +/*------------------------------------------------------------------------- + * + * utility.c + * Contains functions which control the execution of the POSTGRES utility + * commands. At one time acted as an interface between the Lisp and C + * systems. + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/tcop/utility.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/reloptions.h" +#include "access/twophase.h" +#include "access/xact.h" +#include "access/xlog.h" +#include "catalog/catalog.h" +#include "catalog/index.h" +#include "catalog/namespace.h" +#include "catalog/pg_inherits.h" +#include "catalog/toasting.h" +#include "commands/alter.h" +#include "commands/async.h" +#include "commands/cluster.h" +#include "commands/collationcmds.h" +#include "commands/comment.h" +#include "commands/conversioncmds.h" +#include "commands/copy.h" +#include "commands/createas.h" +#include "commands/dbcommands.h" +#include "commands/defrem.h" +#include "commands/discard.h" +#include "commands/event_trigger.h" +#include "commands/explain.h" +#include "commands/extension.h" +#include "commands/lockcmds.h" +#include "commands/matview.h" +#include "commands/policy.h" +#include "commands/portalcmds.h" +#include "commands/prepare.h" +#include "commands/proclang.h" +#include "commands/publicationcmds.h" +#include "commands/schemacmds.h" +#include "commands/seclabel.h" +#include "commands/sequence.h" +#include "commands/subscriptioncmds.h" +#include "commands/tablecmds.h" +#include "commands/tablespace.h" +#include "commands/trigger.h" +#include "commands/typecmds.h" +#include "commands/user.h" +#include "commands/vacuum.h" +#include "commands/view.h" +#include "miscadmin.h" +#include "parser/parse_utilcmd.h" +#include "postmaster/bgwriter.h" +#include "rewrite/rewriteDefine.h" +#include "rewrite/rewriteRemove.h" +#include "storage/fd.h" +#include "tcop/pquery.h" +#include "tcop/utility.h" +#include "utils/acl.h" +#include "utils/guc.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +/* Hook for plugins to get control in ProcessUtility() */ +ProcessUtility_hook_type ProcessUtility_hook = NULL; + +/* local function declarations */ +static int ClassifyUtilityCommandAsReadOnly(Node *parsetree); +static void ProcessUtilitySlow(ParseState *pstate, + PlannedStmt *pstmt, + const char *queryString, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, + QueryCompletion *qc); +static void ExecDropStmt(DropStmt *stmt, bool isTopLevel); + +/* + * CommandIsReadOnly: is an executable query read-only? + * + * This is a much stricter test than we apply for XactReadOnly mode; + * the query must be *in truth* read-only, because the caller wishes + * not to do CommandCounterIncrement for it. + * + * Note: currently no need to support raw or analyzed queries here + */ +bool +CommandIsReadOnly(PlannedStmt *pstmt) +{ + Assert(IsA(pstmt, PlannedStmt)); + switch (pstmt->commandType) + { + case CMD_SELECT: + if (pstmt->rowMarks != NIL) + return false; /* SELECT FOR [KEY] UPDATE/SHARE */ + else if (pstmt->hasModifyingCTE) + return false; /* data-modifying CTE */ + else + return true; + case CMD_UPDATE: + case CMD_INSERT: + case CMD_DELETE: + return false; + case CMD_UTILITY: + /* For now, treat all utility commands as read/write */ + return false; + default: + elog(WARNING, "unrecognized commandType: %d", + (int) pstmt->commandType); + break; + } + return false; +} + +/* + * Determine the degree to which a utility command is read only. + * + * Note the definitions of the relevant flags in src/include/utility/tcop.h. + */ +static int +ClassifyUtilityCommandAsReadOnly(Node *parsetree) +{ + switch (nodeTag(parsetree)) + { + 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_AlterObjectDependsStmt: + case T_AlterObjectSchemaStmt: + 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_AlterTypeStmt: + case T_AlterUserMappingStmt: + case T_CommentStmt: + case T_CompositeTypeStmt: + case T_CreateAmStmt: + case T_CreateCastStmt: + case T_CreateConversionStmt: + 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_CreateOpClassStmt: + case T_CreateOpFamilyStmt: + 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_CreateTableSpaceStmt: + case T_CreateTransformStmt: + case T_CreateTrigStmt: + case T_CreateUserMappingStmt: + case T_CreatedbStmt: + case T_DefineStmt: + case T_DropOwnedStmt: + case T_DropRoleStmt: + case T_DropStmt: + case T_DropSubscriptionStmt: + case T_DropTableSpaceStmt: + case T_DropUserMappingStmt: + 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_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 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; + } + elog(ERROR, "unrecognized TransactionStmtKind: %d", + (int) stmt->kind); + return 0; /* silence stupider compilers */ + } + + default: + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(parsetree)); + return 0; /* silence stupider compilers */ + } +} + +/* + * PreventCommandIfReadOnly: throw error if XactReadOnly + * + * This is useful partly to ensure consistency of the error message wording; + * some 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))); +} + +/* + * PreventCommandIfParallelMode: throw error if current (sub)transaction is + * in parallel mode. + * + * This is useful partly to ensure consistency of the error message wording; + * some callers have checked IsInParallelMode() for themselves. + */ +void +PreventCommandIfParallelMode(const char *cmdname) +{ + if (IsInParallelMode()) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TRANSACTION_STATE), + /* translator: %s is name of a SQL command, eg CREATE */ + errmsg("cannot execute %s during a parallel operation", + cmdname))); +} + +/* + * PreventCommandDuringRecovery: throw error if RecoveryInProgress + * + * The majority of operations that are unsafe in a Hot Standby + * 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 + * inside a security restriction context. + * + * This is needed to protect session-local state for which there is not any + * better-defined protection mechanism, such as ownership. + */ +static void +CheckRestrictedOperation(const char *cmdname) +{ + if (InSecurityRestrictedOperation()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + /* translator: %s is name of a SQL command, eg PREPARE */ + errmsg("cannot execute %s within security-restricted operation", + cmdname))); +} + +/* + * ProcessUtility + * general utility function invoker + * + * pstmt: PlannedStmt wrapper for the utility statement + * queryString: original source text of command + * readOnlyTree: if true, pstmt's node tree must not be modified + * context: identifies source of statement (toplevel client command, + * non-toplevel client command, subcommand of a larger utility command) + * params: parameters to use during execution + * queryEnv: environment for parse through execution (e.g., ephemeral named + * tables like trigger transition tables). May be NULL. + * dest: where to send results + * qc: where to store command completion status data. May be NULL, + * but if not, then caller must have initialized it. + * + * Caller MUST supply a queryString; it is not allowed (anymore) to pass NULL. + * If you really don't have source text, you can pass a constant string, + * perhaps "(query not available)". + * + * Note for users of ProcessUtility_hook: the same queryString may be passed + * to multiple invocations of ProcessUtility when processing a query string + * containing multiple semicolon-separated statements. One should use + * pstmt->stmt_location and pstmt->stmt_len to identify the substring + * containing the current statement. Keep in mind also that some utility + * statements (e.g., CREATE SCHEMA) will recurse to ProcessUtility to process + * sub-statements, often passing down the same queryString, stmt_location, + * and stmt_len that were given for the whole statement. + */ +void +ProcessUtility(PlannedStmt *pstmt, + const char *queryString, + bool readOnlyTree, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, + QueryCompletion *qc) +{ + Assert(IsA(pstmt, PlannedStmt)); + Assert(pstmt->commandType == CMD_UTILITY); + Assert(queryString != NULL); /* required as of 8.4 */ + Assert(qc == NULL || qc->commandTag == CMDTAG_UNKNOWN); + + /* + * We provide a function hook variable that lets loadable plugins get + * control when ProcessUtility is called. Such a plugin would normally + * call standard_ProcessUtility(). + */ + if (ProcessUtility_hook) + (*ProcessUtility_hook) (pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); + else + standard_ProcessUtility(pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); +} + +/* + * standard_ProcessUtility itself deals only with utility commands for + * which we do not provide event trigger support. Commands that do have + * such support are passed down to ProcessUtilitySlow, which contains the + * necessary infrastructure for such triggers. + * + * This division is not just for performance: it's critical that the + * event trigger code not be invoked when doing START TRANSACTION for + * example, because we might need to refresh the event trigger cache, + * which requires being in a valid transaction. + */ +void +standard_ProcessUtility(PlannedStmt *pstmt, + const char *queryString, + bool readOnlyTree, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, + QueryCompletion *qc) +{ + Node *parsetree; + 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(); + + /* + * If the given node tree is read-only, make a copy to ensure that parse + * transformations don't damage the original tree. This could be + * refactored to avoid making unnecessary copies in more cases, but it's + * not clear that it's worth a great deal of trouble over. Statements + * that are complex enough to be expensive to copy are exactly the ones + * we'd need to copy, so that only marginal savings seem possible. + */ + if (readOnlyTree) + pstmt = copyObject(pstmt); + parsetree = pstmt->utilityStmt; + + /* Prohibit read/write commands in read-only states. */ + readonly_flags = ClassifyUtilityCommandAsReadOnly(parsetree); + if (readonly_flags != COMMAND_IS_STRICTLY_READ_ONLY && + (XactReadOnly || IsInParallelMode())) + { + CommandTag commandtag = CreateCommandTag(parsetree); + + if ((readonly_flags & COMMAND_OK_IN_READ_ONLY_TXN) == 0) + PreventCommandIfReadOnly(GetCommandTagName(commandtag)); + if ((readonly_flags & COMMAND_OK_IN_PARALLEL_MODE) == 0) + PreventCommandIfParallelMode(GetCommandTagName(commandtag)); + if ((readonly_flags & COMMAND_OK_IN_RECOVERY) == 0) + PreventCommandDuringRecovery(GetCommandTagName(commandtag)); + } + + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + pstate->p_queryEnv = queryEnv; + + switch (nodeTag(parsetree)) + { + /* + * ******************** transactions ******************** + */ + case T_TransactionStmt: + { + TransactionStmt *stmt = (TransactionStmt *) parsetree; + + switch (stmt->kind) + { + /* + * START TRANSACTION, as defined by SQL99: Identical + * to BEGIN. Same code for both. + */ + case TRANS_STMT_BEGIN: + case TRANS_STMT_START: + { + ListCell *lc; + + BeginTransactionBlock(); + foreach(lc, stmt->options) + { + DefElem *item = (DefElem *) lfirst(lc); + + if (strcmp(item->defname, "transaction_isolation") == 0) + SetPGVariable("transaction_isolation", + list_make1(item->arg), + true); + else if (strcmp(item->defname, "transaction_read_only") == 0) + SetPGVariable("transaction_read_only", + list_make1(item->arg), + true); + else if (strcmp(item->defname, "transaction_deferrable") == 0) + SetPGVariable("transaction_deferrable", + list_make1(item->arg), + true); + } + } + break; + + case TRANS_STMT_COMMIT: + if (!EndTransactionBlock(stmt->chain)) + { + /* report unsuccessful commit in qc */ + if (qc) + SetQueryCompletion(qc, CMDTAG_ROLLBACK, 0); + } + break; + + case TRANS_STMT_PREPARE: + if (!PrepareTransactionBlock(stmt->gid)) + { + /* report unsuccessful commit in qc */ + if (qc) + SetQueryCompletion(qc, CMDTAG_ROLLBACK, 0); + } + break; + + case TRANS_STMT_COMMIT_PREPARED: + PreventInTransactionBlock(isTopLevel, "COMMIT PREPARED"); + FinishPreparedTransaction(stmt->gid, true); + break; + + case TRANS_STMT_ROLLBACK_PREPARED: + PreventInTransactionBlock(isTopLevel, "ROLLBACK PREPARED"); + FinishPreparedTransaction(stmt->gid, false); + break; + + case TRANS_STMT_ROLLBACK: + UserAbortTransactionBlock(stmt->chain); + break; + + case TRANS_STMT_SAVEPOINT: + RequireTransactionBlock(isTopLevel, "SAVEPOINT"); + DefineSavepoint(stmt->savepoint_name); + break; + + case TRANS_STMT_RELEASE: + RequireTransactionBlock(isTopLevel, "RELEASE SAVEPOINT"); + ReleaseSavepoint(stmt->savepoint_name); + break; + + case TRANS_STMT_ROLLBACK_TO: + RequireTransactionBlock(isTopLevel, "ROLLBACK TO SAVEPOINT"); + RollbackToSavepoint(stmt->savepoint_name); + + /* + * CommitTransactionCommand is in charge of + * re-defining the savepoint again + */ + break; + } + } + break; + + /* + * Portal (cursor) manipulation + */ + case T_DeclareCursorStmt: + PerformCursorOpen(pstate, (DeclareCursorStmt *) parsetree, params, + isTopLevel); + break; + + case T_ClosePortalStmt: + { + ClosePortalStmt *stmt = (ClosePortalStmt *) parsetree; + + CheckRestrictedOperation("CLOSE"); + PerformPortalClose(stmt->portalname); + } + break; + + case T_FetchStmt: + PerformPortalFetch((FetchStmt *) parsetree, dest, qc); + break; + + case T_DoStmt: + ExecuteDoStmt((DoStmt *) parsetree, isAtomicContext); + break; + + case T_CreateTableSpaceStmt: + /* no event triggers for global objects */ + PreventInTransactionBlock(isTopLevel, "CREATE TABLESPACE"); + CreateTableSpace((CreateTableSpaceStmt *) parsetree); + break; + + case T_DropTableSpaceStmt: + /* no event triggers for global objects */ + PreventInTransactionBlock(isTopLevel, "DROP TABLESPACE"); + DropTableSpace((DropTableSpaceStmt *) parsetree); + break; + + case T_AlterTableSpaceOptionsStmt: + /* no event triggers for global objects */ + AlterTableSpaceOptions((AlterTableSpaceOptionsStmt *) parsetree); + break; + + case T_TruncateStmt: + ExecuteTruncate((TruncateStmt *) parsetree); + break; + + case T_CopyStmt: + { + uint64 processed; + + DoCopy(pstate, (CopyStmt *) parsetree, + pstmt->stmt_location, pstmt->stmt_len, + &processed); + if (qc) + SetQueryCompletion(qc, CMDTAG_COPY, processed); + } + break; + + case T_PrepareStmt: + CheckRestrictedOperation("PREPARE"); + PrepareQuery(pstate, (PrepareStmt *) parsetree, + pstmt->stmt_location, pstmt->stmt_len); + break; + + case T_ExecuteStmt: + ExecuteQuery(pstate, + (ExecuteStmt *) parsetree, NULL, + params, + dest, qc); + break; + + case T_DeallocateStmt: + CheckRestrictedOperation("DEALLOCATE"); + DeallocateQuery((DeallocateStmt *) parsetree); + break; + + case T_GrantRoleStmt: + /* no event triggers for global objects */ + GrantRole((GrantRoleStmt *) parsetree); + break; + + case T_CreatedbStmt: + /* no event triggers for global objects */ + PreventInTransactionBlock(isTopLevel, "CREATE DATABASE"); + createdb(pstate, (CreatedbStmt *) parsetree); + break; + + case T_AlterDatabaseStmt: + /* no event triggers for global objects */ + AlterDatabase(pstate, (AlterDatabaseStmt *) parsetree, isTopLevel); + break; + + case T_AlterDatabaseSetStmt: + /* no event triggers for global objects */ + AlterDatabaseSet((AlterDatabaseSetStmt *) parsetree); + break; + + case T_DropdbStmt: + /* no event triggers for global objects */ + PreventInTransactionBlock(isTopLevel, "DROP DATABASE"); + DropDatabase(pstate, (DropdbStmt *) parsetree); + break; + + /* Query-level asynchronous notification */ + case T_NotifyStmt: + { + NotifyStmt *stmt = (NotifyStmt *) parsetree; + + Async_Notify(stmt->conditionname, stmt->payload); + } + break; + + case T_ListenStmt: + { + ListenStmt *stmt = (ListenStmt *) parsetree; + + CheckRestrictedOperation("LISTEN"); + + /* + * We don't allow LISTEN in background processes, as there is + * no mechanism for them to collect NOTIFY messages, so they'd + * just block cleanout of the async SLRU indefinitely. + * (Authors of custom background workers could bypass this + * restriction by calling Async_Listen directly, but then it's + * on them to provide some mechanism to process the message + * queue.) Note there seems no reason to forbid UNLISTEN. + */ + if (MyBackendType != B_BACKEND) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + /* translator: %s is name of a SQL command, eg LISTEN */ + errmsg("cannot execute %s within a background process", + "LISTEN"))); + + Async_Listen(stmt->conditionname); + } + break; + + case T_UnlistenStmt: + { + UnlistenStmt *stmt = (UnlistenStmt *) parsetree; + + CheckRestrictedOperation("UNLISTEN"); + if (stmt->conditionname) + Async_Unlisten(stmt->conditionname); + else + Async_UnlistenAll(); + } + break; + + case T_LoadStmt: + { + LoadStmt *stmt = (LoadStmt *) parsetree; + + closeAllVfds(); /* probably not necessary... */ + /* Allowed names are restricted if you're not superuser */ + load_file(stmt->filename, !superuser()); + } + break; + + case T_CallStmt: + ExecuteCallStmt(castNode(CallStmt, parsetree), params, isAtomicContext, dest); + break; + + case T_ClusterStmt: + cluster(pstate, (ClusterStmt *) parsetree, isTopLevel); + break; + + case T_VacuumStmt: + ExecVacuum(pstate, (VacuumStmt *) parsetree, isTopLevel); + break; + + case T_ExplainStmt: + ExplainQuery(pstate, (ExplainStmt *) parsetree, params, dest); + break; + + case T_AlterSystemStmt: + PreventInTransactionBlock(isTopLevel, "ALTER SYSTEM"); + AlterSystemSetConfigFile((AlterSystemStmt *) parsetree); + break; + + case T_VariableSetStmt: + ExecSetVariableStmt((VariableSetStmt *) parsetree, isTopLevel); + break; + + case T_VariableShowStmt: + { + VariableShowStmt *n = (VariableShowStmt *) parsetree; + + GetPGVariable(n->name, dest); + } + break; + + case T_DiscardStmt: + /* should we allow DISCARD PLANS? */ + CheckRestrictedOperation("DISCARD"); + DiscardCommand((DiscardStmt *) parsetree, isTopLevel); + break; + + case T_CreateEventTrigStmt: + /* no event triggers on event triggers */ + CreateEventTrigger((CreateEventTrigStmt *) parsetree); + break; + + case T_AlterEventTrigStmt: + /* no event triggers on event triggers */ + AlterEventTrigger((AlterEventTrigStmt *) parsetree); + break; + + /* + * ******************************** ROLE statements **** + */ + case T_CreateRoleStmt: + /* no event triggers for global objects */ + CreateRole(pstate, (CreateRoleStmt *) parsetree); + break; + + case T_AlterRoleStmt: + /* no event triggers for global objects */ + AlterRole((AlterRoleStmt *) parsetree); + break; + + case T_AlterRoleSetStmt: + /* no event triggers for global objects */ + AlterRoleSet((AlterRoleSetStmt *) parsetree); + break; + + case T_DropRoleStmt: + /* no event triggers for global objects */ + DropRole((DropRoleStmt *) parsetree); + break; + + case T_ReassignOwnedStmt: + /* no event triggers for global objects */ + ReassignOwnedObjects((ReassignOwnedStmt *) parsetree); + break; + + case T_LockStmt: + + /* + * Since the lock would just get dropped immediately, LOCK TABLE + * outside a transaction block is presumed to be user error. + */ + RequireTransactionBlock(isTopLevel, "LOCK TABLE"); + LockTableCommand((LockStmt *) parsetree); + break; + + case T_ConstraintsSetStmt: + WarnNoTransactionBlock(isTopLevel, "SET CONSTRAINTS"); + AfterTriggerSetState((ConstraintsSetStmt *) parsetree); + break; + + case T_CheckPointStmt: + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to do CHECKPOINT"))); + + RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT | + (RecoveryInProgress() ? 0 : CHECKPOINT_FORCE)); + break; + + case T_ReindexStmt: + ExecReindex(pstate, (ReindexStmt *) parsetree, isTopLevel); + break; + + /* + * The following statements are supported by Event Triggers only + * in some cases, so we "fast path" them in the other cases. + */ + + case T_GrantStmt: + { + GrantStmt *stmt = (GrantStmt *) parsetree; + + if (EventTriggerSupportsObjectType(stmt->objtype)) + ProcessUtilitySlow(pstate, pstmt, queryString, + context, params, queryEnv, + dest, qc); + else + ExecuteGrantStmt(stmt); + } + break; + + case T_DropStmt: + { + DropStmt *stmt = (DropStmt *) parsetree; + + if (EventTriggerSupportsObjectType(stmt->removeType)) + ProcessUtilitySlow(pstate, pstmt, queryString, + context, params, queryEnv, + dest, qc); + else + ExecDropStmt(stmt, isTopLevel); + } + break; + + case T_RenameStmt: + { + RenameStmt *stmt = (RenameStmt *) parsetree; + + if (EventTriggerSupportsObjectType(stmt->renameType)) + ProcessUtilitySlow(pstate, pstmt, queryString, + context, params, queryEnv, + dest, qc); + else + ExecRenameStmt(stmt); + } + break; + + case T_AlterObjectDependsStmt: + { + AlterObjectDependsStmt *stmt = (AlterObjectDependsStmt *) parsetree; + + if (EventTriggerSupportsObjectType(stmt->objectType)) + ProcessUtilitySlow(pstate, pstmt, queryString, + context, params, queryEnv, + dest, qc); + else + ExecAlterObjectDependsStmt(stmt, NULL); + } + break; + + case T_AlterObjectSchemaStmt: + { + AlterObjectSchemaStmt *stmt = (AlterObjectSchemaStmt *) parsetree; + + if (EventTriggerSupportsObjectType(stmt->objectType)) + ProcessUtilitySlow(pstate, pstmt, queryString, + context, params, queryEnv, + dest, qc); + else + ExecAlterObjectSchemaStmt(stmt, NULL); + } + break; + + case T_AlterOwnerStmt: + { + AlterOwnerStmt *stmt = (AlterOwnerStmt *) parsetree; + + if (EventTriggerSupportsObjectType(stmt->objectType)) + ProcessUtilitySlow(pstate, pstmt, queryString, + context, params, queryEnv, + dest, qc); + else + ExecAlterOwnerStmt(stmt); + } + break; + + case T_CommentStmt: + { + CommentStmt *stmt = (CommentStmt *) parsetree; + + if (EventTriggerSupportsObjectType(stmt->objtype)) + ProcessUtilitySlow(pstate, pstmt, queryString, + context, params, queryEnv, + dest, qc); + else + CommentObject(stmt); + break; + } + + case T_SecLabelStmt: + { + SecLabelStmt *stmt = (SecLabelStmt *) parsetree; + + if (EventTriggerSupportsObjectType(stmt->objtype)) + ProcessUtilitySlow(pstate, pstmt, queryString, + context, params, queryEnv, + dest, qc); + else + ExecSecLabelStmt(stmt); + break; + } + + default: + /* All other statement types have event trigger support */ + ProcessUtilitySlow(pstate, pstmt, queryString, + context, params, queryEnv, + dest, qc); + break; + } + + free_parsestate(pstate); + + /* + * Make effects of commands visible, for instance so that + * PreCommit_on_commit_actions() can see them (see for example bug + * #15631). + */ + CommandCounterIncrement(); +} + +/* + * The "Slow" variant of ProcessUtility should only receive statements + * supported by the event triggers facility. Therefore, we always + * perform the trigger support calls if the context allows it. + */ +static void +ProcessUtilitySlow(ParseState *pstate, + PlannedStmt *pstmt, + const char *queryString, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, + QueryCompletion *qc) +{ + Node *parsetree = pstmt->utilityStmt; + bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL); + bool isCompleteQuery = (context != PROCESS_UTILITY_SUBCOMMAND); + bool needCleanup; + bool commandCollected = false; + ObjectAddress address; + ObjectAddress secondaryObject = InvalidObjectAddress; + + /* All event trigger calls are done only when isCompleteQuery is true */ + needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery(); + + /* PG_TRY block is to ensure we call EventTriggerEndCompleteQuery */ + PG_TRY(); + { + if (isCompleteQuery) + EventTriggerDDLCommandStart(parsetree); + + switch (nodeTag(parsetree)) + { + /* + * relation and attribute manipulation + */ + case T_CreateSchemaStmt: + CreateSchemaCommand((CreateSchemaStmt *) parsetree, + queryString, + pstmt->stmt_location, + pstmt->stmt_len); + + /* + * EventTriggerCollectSimpleCommand called by + * CreateSchemaCommand + */ + commandCollected = true; + break; + + case T_CreateStmt: + case T_CreateForeignTableStmt: + { + List *stmts; + RangeVar *table_rv = NULL; + + /* Run parse analysis ... */ + stmts = transformCreateStmt((CreateStmt *) parsetree, + queryString); + + /* + * ... and do it. We can't use foreach() because we may + * modify the list midway through, so pick off the + * elements one at a time, the hard way. + */ + while (stmts != NIL) + { + Node *stmt = (Node *) linitial(stmts); + + stmts = list_delete_first(stmts); + + if (IsA(stmt, CreateStmt)) + { + CreateStmt *cstmt = (CreateStmt *) stmt; + Datum toast_options; + static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + + /* Remember transformed RangeVar for LIKE */ + table_rv = cstmt->relation; + + /* Create the table itself */ + address = DefineRelation(cstmt, + RELKIND_RELATION, + InvalidOid, NULL, + queryString); + EventTriggerCollectSimpleCommand(address, + secondaryObject, + stmt); + + /* + * Let NewRelationCreateToastTable decide if this + * one needs a secondary relation too. + */ + CommandCounterIncrement(); + + /* + * parse and validate reloptions for the toast + * table + */ + toast_options = transformRelOptions((Datum) 0, + cstmt->options, + "toast", + validnsps, + true, + false); + (void) heap_reloptions(RELKIND_TOASTVALUE, + toast_options, + true); + + NewRelationCreateToastTable(address.objectId, + toast_options); + } + else if (IsA(stmt, CreateForeignTableStmt)) + { + CreateForeignTableStmt *cstmt = (CreateForeignTableStmt *) stmt; + + /* Remember transformed RangeVar for LIKE */ + table_rv = cstmt->base.relation; + + /* Create the table itself */ + address = DefineRelation(&cstmt->base, + RELKIND_FOREIGN_TABLE, + InvalidOid, NULL, + queryString); + CreateForeignTable(cstmt, + address.objectId); + EventTriggerCollectSimpleCommand(address, + secondaryObject, + stmt); + } + else if (IsA(stmt, TableLikeClause)) + { + /* + * Do delayed processing of LIKE options. This + * will result in additional sub-statements for us + * to process. Those should get done before any + * remaining actions, so prepend them to "stmts". + */ + TableLikeClause *like = (TableLikeClause *) stmt; + List *morestmts; + + Assert(table_rv != NULL); + + morestmts = expandTableLikeClause(table_rv, like); + stmts = list_concat(morestmts, stmts); + } + else + { + /* + * Recurse for anything else. Note the recursive + * call will stash the objects so created into our + * event trigger context. + */ + PlannedStmt *wrapper; + + wrapper = makeNode(PlannedStmt); + wrapper->commandType = CMD_UTILITY; + wrapper->canSetTag = false; + wrapper->utilityStmt = stmt; + wrapper->stmt_location = pstmt->stmt_location; + wrapper->stmt_len = pstmt->stmt_len; + + ProcessUtility(wrapper, + queryString, + false, + PROCESS_UTILITY_SUBCOMMAND, + params, + NULL, + None_Receiver, + NULL); + } + + /* Need CCI between commands */ + if (stmts != NIL) + CommandCounterIncrement(); + } + + /* + * The multiple commands generated here are stashed + * individually, so disable collection below. + */ + commandCollected = true; + } + break; + + case T_AlterTableStmt: + { + AlterTableStmt *atstmt = (AlterTableStmt *) parsetree; + Oid relid; + LOCKMODE lockmode; + ListCell *cell; + + /* + * Disallow ALTER TABLE .. DETACH CONCURRENTLY in a + * transaction block or function. (Perhaps it could be + * allowed in a procedure, but don't hold your breath.) + */ + foreach(cell, atstmt->cmds) + { + AlterTableCmd *cmd = (AlterTableCmd *) lfirst(cell); + + /* Disallow DETACH CONCURRENTLY in a transaction block */ + if (cmd->subtype == AT_DetachPartition) + { + if (((PartitionCmd *) cmd->def)->concurrent) + PreventInTransactionBlock(isTopLevel, + "ALTER TABLE ... DETACH CONCURRENTLY"); + } + } + + /* + * Figure out lock mode, and acquire lock. This also does + * basic permissions checks, so that we won't wait for a + * lock on (for example) a relation on which we have no + * permissions. + */ + lockmode = AlterTableGetLockLevel(atstmt->cmds); + relid = AlterTableLookupRelation(atstmt, lockmode); + + if (OidIsValid(relid)) + { + AlterTableUtilityContext atcontext; + + /* Set up info needed for recursive callbacks ... */ + atcontext.pstmt = pstmt; + atcontext.queryString = queryString; + atcontext.relid = relid; + atcontext.params = params; + atcontext.queryEnv = queryEnv; + + /* ... ensure we have an event trigger context ... */ + EventTriggerAlterTableStart(parsetree); + EventTriggerAlterTableRelid(relid); + + /* ... and do it */ + AlterTable(atstmt, lockmode, &atcontext); + + /* done */ + EventTriggerAlterTableEnd(); + } + else + ereport(NOTICE, + (errmsg("relation \"%s\" does not exist, skipping", + atstmt->relation->relname))); + } + + /* ALTER TABLE stashes commands internally */ + commandCollected = true; + break; + + case T_AlterDomainStmt: + { + AlterDomainStmt *stmt = (AlterDomainStmt *) parsetree; + + /* + * Some or all of these functions are recursive to cover + * inherited things, so permission checks are done there. + */ + switch (stmt->subtype) + { + case 'T': /* ALTER DOMAIN DEFAULT */ + + /* + * Recursively alter column default for table and, + * if requested, for descendants + */ + address = + AlterDomainDefault(stmt->typeName, + stmt->def); + break; + case 'N': /* ALTER DOMAIN DROP NOT NULL */ + address = + AlterDomainNotNull(stmt->typeName, + false); + break; + case 'O': /* ALTER DOMAIN SET NOT NULL */ + address = + AlterDomainNotNull(stmt->typeName, + true); + break; + case 'C': /* ADD CONSTRAINT */ + address = + AlterDomainAddConstraint(stmt->typeName, + stmt->def, + &secondaryObject); + break; + case 'X': /* DROP CONSTRAINT */ + address = + AlterDomainDropConstraint(stmt->typeName, + stmt->name, + stmt->behavior, + stmt->missing_ok); + break; + case 'V': /* VALIDATE CONSTRAINT */ + address = + AlterDomainValidateConstraint(stmt->typeName, + stmt->name); + break; + default: /* oops */ + elog(ERROR, "unrecognized alter domain type: %d", + (int) stmt->subtype); + break; + } + } + break; + + /* + * ************* object creation / destruction ************** + */ + case T_DefineStmt: + { + DefineStmt *stmt = (DefineStmt *) parsetree; + + switch (stmt->kind) + { + case OBJECT_AGGREGATE: + address = + DefineAggregate(pstate, stmt->defnames, stmt->args, + stmt->oldstyle, + stmt->definition, + stmt->replace); + break; + case OBJECT_OPERATOR: + Assert(stmt->args == NIL); + address = DefineOperator(stmt->defnames, + stmt->definition); + break; + case OBJECT_TYPE: + Assert(stmt->args == NIL); + address = DefineType(pstate, + stmt->defnames, + stmt->definition); + break; + case OBJECT_TSPARSER: + Assert(stmt->args == NIL); + address = DefineTSParser(stmt->defnames, + stmt->definition); + break; + case OBJECT_TSDICTIONARY: + Assert(stmt->args == NIL); + address = DefineTSDictionary(stmt->defnames, + stmt->definition); + break; + case OBJECT_TSTEMPLATE: + Assert(stmt->args == NIL); + address = DefineTSTemplate(stmt->defnames, + stmt->definition); + break; + case OBJECT_TSCONFIGURATION: + Assert(stmt->args == NIL); + address = DefineTSConfiguration(stmt->defnames, + stmt->definition, + &secondaryObject); + break; + case OBJECT_COLLATION: + Assert(stmt->args == NIL); + address = DefineCollation(pstate, + stmt->defnames, + stmt->definition, + stmt->if_not_exists); + break; + default: + elog(ERROR, "unrecognized define stmt type: %d", + (int) stmt->kind); + break; + } + } + break; + + case T_IndexStmt: /* CREATE INDEX */ + { + IndexStmt *stmt = (IndexStmt *) parsetree; + Oid relid; + LOCKMODE lockmode; + bool is_alter_table; + + if (stmt->concurrent) + PreventInTransactionBlock(isTopLevel, + "CREATE INDEX CONCURRENTLY"); + + /* + * Look up the relation OID just once, right here at the + * beginning, so that we don't end up repeating the name + * lookup later and latching onto a different relation + * partway through. To avoid lock upgrade hazards, it's + * important that we take the strongest lock that will + * eventually be needed here, so the lockmode calculation + * needs to match what DefineIndex() does. + */ + lockmode = stmt->concurrent ? ShareUpdateExclusiveLock + : ShareLock; + relid = + RangeVarGetRelidExtended(stmt->relation, lockmode, + 0, + RangeVarCallbackOwnsRelation, + NULL); + + /* + * CREATE INDEX on partitioned tables (but not regular + * inherited tables) recurses to partitions, so we must + * acquire locks early to avoid deadlocks. + * + * We also take the opportunity to verify that all + * partitions are something we can put an index on, to + * avoid building some indexes only to fail later. + */ + if (stmt->relation->inh && + get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE) + { + ListCell *lc; + List *inheritors = NIL; + + inheritors = find_all_inheritors(relid, lockmode, NULL); + foreach(lc, inheritors) + { + char relkind = get_rel_relkind(lfirst_oid(lc)); + + if (relkind != RELKIND_RELATION && + relkind != RELKIND_MATVIEW && + relkind != RELKIND_PARTITIONED_TABLE && + relkind != RELKIND_FOREIGN_TABLE) + elog(ERROR, "unexpected relkind \"%c\" on partition \"%s\"", + relkind, stmt->relation->relname); + + if (relkind == RELKIND_FOREIGN_TABLE && + (stmt->unique || stmt->primary)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot create unique index on partitioned table \"%s\"", + stmt->relation->relname), + errdetail("Table \"%s\" contains partitions that are foreign tables.", + stmt->relation->relname))); + } + list_free(inheritors); + } + + /* + * If the IndexStmt is already transformed, it must have + * come from generateClonedIndexStmt, which in current + * usage means it came from expandTableLikeClause rather + * than from original parse analysis. And that means we + * must treat it like ALTER TABLE ADD INDEX, not CREATE. + * (This is a bit grotty, but currently it doesn't seem + * worth adding a separate bool field for the purpose.) + */ + is_alter_table = stmt->transformed; + + /* Run parse analysis ... */ + stmt = transformIndexStmt(relid, stmt, queryString); + + /* ... and do it */ + EventTriggerAlterTableStart(parsetree); + address = + DefineIndex(relid, /* OID of heap relation */ + stmt, + InvalidOid, /* no predefined OID */ + InvalidOid, /* no parent index */ + InvalidOid, /* no parent constraint */ + is_alter_table, + true, /* check_rights */ + true, /* check_not_in_use */ + false, /* skip_build */ + false); /* quiet */ + + /* + * Add the CREATE INDEX node itself to stash right away; + * if there were any commands stashed in the ALTER TABLE + * code, we need them to appear after this one. + */ + EventTriggerCollectSimpleCommand(address, secondaryObject, + parsetree); + commandCollected = true; + EventTriggerAlterTableEnd(); + } + break; + + case T_CreateExtensionStmt: + address = CreateExtension(pstate, (CreateExtensionStmt *) parsetree); + break; + + case T_AlterExtensionStmt: + address = ExecAlterExtensionStmt(pstate, (AlterExtensionStmt *) parsetree); + break; + + case T_AlterExtensionContentsStmt: + address = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree, + &secondaryObject); + break; + + case T_CreateFdwStmt: + address = CreateForeignDataWrapper((CreateFdwStmt *) parsetree); + break; + + case T_AlterFdwStmt: + address = AlterForeignDataWrapper((AlterFdwStmt *) parsetree); + break; + + case T_CreateForeignServerStmt: + address = CreateForeignServer((CreateForeignServerStmt *) parsetree); + break; + + case T_AlterForeignServerStmt: + address = AlterForeignServer((AlterForeignServerStmt *) parsetree); + break; + + case T_CreateUserMappingStmt: + address = CreateUserMapping((CreateUserMappingStmt *) parsetree); + break; + + case T_AlterUserMappingStmt: + address = AlterUserMapping((AlterUserMappingStmt *) parsetree); + break; + + case T_DropUserMappingStmt: + RemoveUserMapping((DropUserMappingStmt *) parsetree); + /* no commands stashed for DROP */ + commandCollected = true; + break; + + case T_ImportForeignSchemaStmt: + ImportForeignSchema((ImportForeignSchemaStmt *) parsetree); + /* commands are stashed inside ImportForeignSchema */ + commandCollected = true; + break; + + case T_CompositeTypeStmt: /* CREATE TYPE (composite) */ + { + CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree; + + address = DefineCompositeType(stmt->typevar, + stmt->coldeflist); + } + break; + + case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ + address = DefineEnum((CreateEnumStmt *) parsetree); + break; + + case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ + address = DefineRange((CreateRangeStmt *) parsetree); + break; + + case T_AlterEnumStmt: /* ALTER TYPE (enum) */ + address = AlterEnum((AlterEnumStmt *) parsetree); + break; + + case T_ViewStmt: /* CREATE VIEW */ + EventTriggerAlterTableStart(parsetree); + address = DefineView((ViewStmt *) parsetree, queryString, + pstmt->stmt_location, pstmt->stmt_len); + EventTriggerCollectSimpleCommand(address, secondaryObject, + parsetree); + /* stashed internally */ + commandCollected = true; + EventTriggerAlterTableEnd(); + break; + + case T_CreateFunctionStmt: /* CREATE FUNCTION */ + address = CreateFunction(pstate, (CreateFunctionStmt *) parsetree); + break; + + case T_AlterFunctionStmt: /* ALTER FUNCTION */ + address = AlterFunction(pstate, (AlterFunctionStmt *) parsetree); + break; + + case T_RuleStmt: /* CREATE RULE */ + address = DefineRule((RuleStmt *) parsetree, queryString); + break; + + case T_CreateSeqStmt: + address = DefineSequence(pstate, (CreateSeqStmt *) parsetree); + break; + + case T_AlterSeqStmt: + address = AlterSequence(pstate, (AlterSeqStmt *) parsetree); + break; + + case T_CreateTableAsStmt: + address = ExecCreateTableAs(pstate, (CreateTableAsStmt *) parsetree, + params, queryEnv, qc); + break; + + case T_RefreshMatViewStmt: + + /* + * REFRESH CONCURRENTLY executes some DDL commands internally. + * Inhibit DDL command collection here to avoid those commands + * from showing up in the deparsed command queue. The refresh + * command itself is queued, which is enough. + */ + EventTriggerInhibitCommandCollection(); + PG_TRY(); + { + address = ExecRefreshMatView((RefreshMatViewStmt *) parsetree, + queryString, params, qc); + } + PG_FINALLY(); + { + EventTriggerUndoInhibitCommandCollection(); + } + PG_END_TRY(); + break; + + case T_CreateTrigStmt: + address = CreateTrigger((CreateTrigStmt *) parsetree, + queryString, InvalidOid, InvalidOid, + InvalidOid, InvalidOid, InvalidOid, + InvalidOid, NULL, false, false); + break; + + case T_CreatePLangStmt: + address = CreateProceduralLanguage((CreatePLangStmt *) parsetree); + break; + + case T_CreateDomainStmt: + address = DefineDomain((CreateDomainStmt *) parsetree); + break; + + case T_CreateConversionStmt: + address = CreateConversionCommand((CreateConversionStmt *) parsetree); + break; + + case T_CreateCastStmt: + address = CreateCast((CreateCastStmt *) parsetree); + break; + + case T_CreateOpClassStmt: + DefineOpClass((CreateOpClassStmt *) parsetree); + /* command is stashed in DefineOpClass */ + commandCollected = true; + break; + + case T_CreateOpFamilyStmt: + address = DefineOpFamily((CreateOpFamilyStmt *) parsetree); + + /* + * DefineOpFamily calls EventTriggerCollectSimpleCommand + * directly. + */ + commandCollected = true; + break; + + case T_CreateTransformStmt: + address = CreateTransform((CreateTransformStmt *) parsetree); + break; + + case T_AlterOpFamilyStmt: + AlterOpFamily((AlterOpFamilyStmt *) parsetree); + /* commands are stashed in AlterOpFamily */ + commandCollected = true; + break; + + case T_AlterTSDictionaryStmt: + address = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree); + break; + + case T_AlterTSConfigurationStmt: + AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree); + + /* + * Commands are stashed in MakeConfigurationMapping and + * DropConfigurationMapping, which are called from + * AlterTSConfiguration + */ + commandCollected = true; + break; + + case T_AlterTableMoveAllStmt: + AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree); + /* commands are stashed in AlterTableMoveAll */ + commandCollected = true; + break; + + case T_DropStmt: + ExecDropStmt((DropStmt *) parsetree, isTopLevel); + /* no commands stashed for DROP */ + commandCollected = true; + break; + + case T_RenameStmt: + address = ExecRenameStmt((RenameStmt *) parsetree); + break; + + case T_AlterObjectDependsStmt: + address = + ExecAlterObjectDependsStmt((AlterObjectDependsStmt *) parsetree, + &secondaryObject); + break; + + case T_AlterObjectSchemaStmt: + address = + ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree, + &secondaryObject); + break; + + case T_AlterOwnerStmt: + address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree); + break; + + case T_AlterOperatorStmt: + address = AlterOperator((AlterOperatorStmt *) parsetree); + break; + + case T_AlterTypeStmt: + address = AlterType((AlterTypeStmt *) parsetree); + break; + + case T_CommentStmt: + address = CommentObject((CommentStmt *) parsetree); + break; + + case T_GrantStmt: + ExecuteGrantStmt((GrantStmt *) parsetree); + /* commands are stashed in ExecGrantStmt_oids */ + commandCollected = true; + break; + + case T_DropOwnedStmt: + DropOwnedObjects((DropOwnedStmt *) parsetree); + /* no commands stashed for DROP */ + commandCollected = true; + break; + + case T_AlterDefaultPrivilegesStmt: + ExecAlterDefaultPrivilegesStmt(pstate, (AlterDefaultPrivilegesStmt *) parsetree); + EventTriggerCollectAlterDefPrivs((AlterDefaultPrivilegesStmt *) parsetree); + commandCollected = true; + break; + + case T_CreatePolicyStmt: /* CREATE POLICY */ + address = CreatePolicy((CreatePolicyStmt *) parsetree); + break; + + case T_AlterPolicyStmt: /* ALTER POLICY */ + address = AlterPolicy((AlterPolicyStmt *) parsetree); + break; + + case T_SecLabelStmt: + address = ExecSecLabelStmt((SecLabelStmt *) parsetree); + break; + + case T_CreateAmStmt: + address = CreateAccessMethod((CreateAmStmt *) parsetree); + break; + + case T_CreatePublicationStmt: + address = CreatePublication((CreatePublicationStmt *) parsetree); + break; + + case T_AlterPublicationStmt: + AlterPublication((AlterPublicationStmt *) parsetree); + + /* + * AlterPublication calls EventTriggerCollectSimpleCommand + * directly + */ + commandCollected = true; + break; + + case T_CreateSubscriptionStmt: + address = CreateSubscription((CreateSubscriptionStmt *) parsetree, + isTopLevel); + break; + + case T_AlterSubscriptionStmt: + address = AlterSubscription((AlterSubscriptionStmt *) parsetree, + isTopLevel); + break; + + case T_DropSubscriptionStmt: + DropSubscription((DropSubscriptionStmt *) parsetree, isTopLevel); + /* no commands stashed for DROP */ + commandCollected = true; + break; + + case T_CreateStatsStmt: + { + Oid relid; + CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; + RangeVar *rel = (RangeVar *) linitial(stmt->relations); + + if (!IsA(rel, RangeVar)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS"))); + + /* + * CREATE STATISTICS will influence future execution plans + * but does not interfere with currently executing plans. + * So it should be enough to take ShareUpdateExclusiveLock + * on relation, conflicting with ANALYZE and other DDL + * that sets statistical information, but not with normal + * queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, to + * keep the same behavior as before. + */ + relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + + /* Run parse analysis ... */ + stmt = transformStatsStmt(relid, stmt, queryString); + + address = CreateStatistics(stmt); + } + break; + + case T_AlterStatsStmt: + address = AlterStatistics((AlterStatsStmt *) parsetree); + break; + + case T_AlterCollationStmt: + address = AlterCollation((AlterCollationStmt *) parsetree); + break; + + default: + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(parsetree)); + break; + } + + /* + * Remember the object so that ddl_command_end event triggers have + * access to it. + */ + if (!commandCollected) + EventTriggerCollectSimpleCommand(address, secondaryObject, + parsetree); + + if (isCompleteQuery) + { + EventTriggerSQLDrop(parsetree); + EventTriggerDDLCommandEnd(parsetree); + } + } + PG_FINALLY(); + { + if (needCleanup) + EventTriggerEndCompleteQuery(); + } + PG_END_TRY(); +} + +/* + * ProcessUtilityForAlterTable + * Recursive entry from ALTER TABLE + * + * ALTER TABLE sometimes generates subcommands such as CREATE INDEX. + * It calls this, not the main entry point ProcessUtility, to execute + * such subcommands. + * + * stmt: the utility command to execute + * context: opaque passthrough struct with the info we need + * + * It's caller's responsibility to do CommandCounterIncrement after + * calling this, if needed. + */ +void +ProcessUtilityForAlterTable(Node *stmt, AlterTableUtilityContext *context) +{ + PlannedStmt *wrapper; + + /* + * For event triggers, we must "close" the current complex-command set, + * and start a new one afterwards; this is needed to ensure the ordering + * of command events is consistent with the way they were executed. + */ + EventTriggerAlterTableEnd(); + + /* Create a suitable wrapper */ + wrapper = makeNode(PlannedStmt); + wrapper->commandType = CMD_UTILITY; + wrapper->canSetTag = false; + wrapper->utilityStmt = stmt; + wrapper->stmt_location = context->pstmt->stmt_location; + wrapper->stmt_len = context->pstmt->stmt_len; + + ProcessUtility(wrapper, + context->queryString, + false, + PROCESS_UTILITY_SUBCOMMAND, + context->params, + context->queryEnv, + None_Receiver, + NULL); + + EventTriggerAlterTableStart(context->pstmt->utilityStmt); + EventTriggerAlterTableRelid(context->relid); +} + +/* + * Dispatch function for DropStmt + */ +static void +ExecDropStmt(DropStmt *stmt, bool isTopLevel) +{ + switch (stmt->removeType) + { + case OBJECT_INDEX: + if (stmt->concurrent) + PreventInTransactionBlock(isTopLevel, + "DROP INDEX CONCURRENTLY"); + /* fall through */ + + case OBJECT_TABLE: + case OBJECT_SEQUENCE: + case OBJECT_VIEW: + case OBJECT_MATVIEW: + case OBJECT_FOREIGN_TABLE: + RemoveRelations(stmt); + break; + default: + RemoveObjects(stmt); + break; + } +} + + +/* + * UtilityReturnsTuples + * Return "true" if this utility statement will send output to the + * destination. + * + * Generally, there should be a case here for each case in ProcessUtility + * where "dest" is passed on. + */ +bool +UtilityReturnsTuples(Node *parsetree) +{ + switch (nodeTag(parsetree)) + { + case T_CallStmt: + { + CallStmt *stmt = (CallStmt *) parsetree; + + return (stmt->funcexpr->funcresulttype == RECORDOID); + } + case T_FetchStmt: + { + FetchStmt *stmt = (FetchStmt *) parsetree; + Portal portal; + + if (stmt->ismove) + return false; + portal = GetPortalByName(stmt->portalname); + if (!PortalIsValid(portal)) + return false; /* not our business to raise error */ + return portal->tupDesc ? true : false; + } + + case T_ExecuteStmt: + { + ExecuteStmt *stmt = (ExecuteStmt *) parsetree; + PreparedStatement *entry; + + entry = FetchPreparedStatement(stmt->name, false); + if (!entry) + return false; /* not our business to raise error */ + if (entry->plansource->resultDesc) + return true; + return false; + } + + case T_ExplainStmt: + return true; + + case T_VariableShowStmt: + return true; + + default: + return false; + } +} + +/* + * UtilityTupleDescriptor + * Fetch the actual output tuple descriptor for a utility statement + * for which UtilityReturnsTuples() previously returned "true". + * + * The returned descriptor is created in (or copied into) the current memory + * context. + */ +TupleDesc +UtilityTupleDescriptor(Node *parsetree) +{ + switch (nodeTag(parsetree)) + { + case T_CallStmt: + return CallStmtResultDesc((CallStmt *) parsetree); + + case T_FetchStmt: + { + FetchStmt *stmt = (FetchStmt *) parsetree; + Portal portal; + + if (stmt->ismove) + return NULL; + portal = GetPortalByName(stmt->portalname); + if (!PortalIsValid(portal)) + return NULL; /* not our business to raise error */ + return CreateTupleDescCopy(portal->tupDesc); + } + + case T_ExecuteStmt: + { + ExecuteStmt *stmt = (ExecuteStmt *) parsetree; + PreparedStatement *entry; + + entry = FetchPreparedStatement(stmt->name, false); + if (!entry) + return NULL; /* not our business to raise error */ + return FetchPreparedStatementResultDesc(entry); + } + + case T_ExplainStmt: + return ExplainResultDesc((ExplainStmt *) parsetree); + + case T_VariableShowStmt: + { + VariableShowStmt *n = (VariableShowStmt *) parsetree; + + return GetPGVariableResultDesc(n->name); + } + + default: + return NULL; + } +} + + +/* + * QueryReturnsTuples + * Return "true" if this Query will send output to the destination. + */ +#ifdef NOT_USED +bool +QueryReturnsTuples(Query *parsetree) +{ + switch (parsetree->commandType) + { + case CMD_SELECT: + /* returns tuples */ + return true; + case CMD_INSERT: + case CMD_UPDATE: + case CMD_DELETE: + /* the forms with RETURNING return tuples */ + if (parsetree->returningList) + return true; + break; + case CMD_UTILITY: + return UtilityReturnsTuples(parsetree->utilityStmt); + case CMD_UNKNOWN: + case CMD_NOTHING: + /* probably shouldn't get here */ + break; + } + return false; /* default */ +} +#endif + + +/* + * UtilityContainsQuery + * Return the contained Query, or NULL if there is none + * + * Certain utility statements, such as EXPLAIN, contain a plannable Query. + * This function encapsulates knowledge of exactly which ones do. + * We assume it is invoked only on already-parse-analyzed statements + * (else the contained parsetree isn't a Query yet). + * + * In some cases (currently, only EXPLAIN of CREATE TABLE AS/SELECT INTO and + * CREATE MATERIALIZED VIEW), potentially Query-containing utility statements + * can be nested. This function will drill down to a non-utility Query, or + * return NULL if none. + */ +Query * +UtilityContainsQuery(Node *parsetree) +{ + Query *qry; + + switch (nodeTag(parsetree)) + { + case T_DeclareCursorStmt: + qry = castNode(Query, ((DeclareCursorStmt *) parsetree)->query); + if (qry->commandType == CMD_UTILITY) + return UtilityContainsQuery(qry->utilityStmt); + return qry; + + case T_ExplainStmt: + qry = castNode(Query, ((ExplainStmt *) parsetree)->query); + if (qry->commandType == CMD_UTILITY) + return UtilityContainsQuery(qry->utilityStmt); + return qry; + + case T_CreateTableAsStmt: + qry = castNode(Query, ((CreateTableAsStmt *) parsetree)->query); + if (qry->commandType == CMD_UTILITY) + return UtilityContainsQuery(qry->utilityStmt); + return qry; + + default: + return NULL; + } +} + + +/* + * AlterObjectTypeCommandTag + * helper function for CreateCommandTag + * + * This covers most cases where ALTER is used with an ObjectType enum. + */ +static CommandTag +AlterObjectTypeCommandTag(ObjectType objtype) +{ + CommandTag tag; + + switch (objtype) + { + case OBJECT_AGGREGATE: + tag = CMDTAG_ALTER_AGGREGATE; + break; + case OBJECT_ATTRIBUTE: + tag = CMDTAG_ALTER_TYPE; + break; + case OBJECT_CAST: + tag = CMDTAG_ALTER_CAST; + break; + case OBJECT_COLLATION: + tag = CMDTAG_ALTER_COLLATION; + break; + case OBJECT_COLUMN: + tag = CMDTAG_ALTER_TABLE; + break; + case OBJECT_CONVERSION: + tag = CMDTAG_ALTER_CONVERSION; + break; + case OBJECT_DATABASE: + tag = CMDTAG_ALTER_DATABASE; + break; + case OBJECT_DOMAIN: + case OBJECT_DOMCONSTRAINT: + tag = CMDTAG_ALTER_DOMAIN; + break; + case OBJECT_EXTENSION: + tag = CMDTAG_ALTER_EXTENSION; + break; + case OBJECT_FDW: + tag = CMDTAG_ALTER_FOREIGN_DATA_WRAPPER; + break; + case OBJECT_FOREIGN_SERVER: + tag = CMDTAG_ALTER_SERVER; + break; + case OBJECT_FOREIGN_TABLE: + tag = CMDTAG_ALTER_FOREIGN_TABLE; + break; + case OBJECT_FUNCTION: + tag = CMDTAG_ALTER_FUNCTION; + break; + case OBJECT_INDEX: + tag = CMDTAG_ALTER_INDEX; + break; + case OBJECT_LANGUAGE: + tag = CMDTAG_ALTER_LANGUAGE; + break; + case OBJECT_LARGEOBJECT: + tag = CMDTAG_ALTER_LARGE_OBJECT; + break; + case OBJECT_OPCLASS: + tag = CMDTAG_ALTER_OPERATOR_CLASS; + break; + case OBJECT_OPERATOR: + tag = CMDTAG_ALTER_OPERATOR; + break; + case OBJECT_OPFAMILY: + tag = CMDTAG_ALTER_OPERATOR_FAMILY; + break; + case OBJECT_POLICY: + tag = CMDTAG_ALTER_POLICY; + break; + case OBJECT_PROCEDURE: + tag = CMDTAG_ALTER_PROCEDURE; + break; + case OBJECT_ROLE: + tag = CMDTAG_ALTER_ROLE; + break; + case OBJECT_ROUTINE: + tag = CMDTAG_ALTER_ROUTINE; + break; + case OBJECT_RULE: + tag = CMDTAG_ALTER_RULE; + break; + case OBJECT_SCHEMA: + tag = CMDTAG_ALTER_SCHEMA; + break; + case OBJECT_SEQUENCE: + tag = CMDTAG_ALTER_SEQUENCE; + break; + case OBJECT_TABLE: + case OBJECT_TABCONSTRAINT: + tag = CMDTAG_ALTER_TABLE; + break; + case OBJECT_TABLESPACE: + tag = CMDTAG_ALTER_TABLESPACE; + break; + case OBJECT_TRIGGER: + tag = CMDTAG_ALTER_TRIGGER; + break; + case OBJECT_EVENT_TRIGGER: + tag = CMDTAG_ALTER_EVENT_TRIGGER; + break; + case OBJECT_TSCONFIGURATION: + tag = CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION; + break; + case OBJECT_TSDICTIONARY: + tag = CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY; + break; + case OBJECT_TSPARSER: + tag = CMDTAG_ALTER_TEXT_SEARCH_PARSER; + break; + case OBJECT_TSTEMPLATE: + tag = CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE; + break; + case OBJECT_TYPE: + tag = CMDTAG_ALTER_TYPE; + break; + case OBJECT_VIEW: + tag = CMDTAG_ALTER_VIEW; + break; + case OBJECT_MATVIEW: + tag = CMDTAG_ALTER_MATERIALIZED_VIEW; + break; + case OBJECT_PUBLICATION: + tag = CMDTAG_ALTER_PUBLICATION; + break; + case OBJECT_SUBSCRIPTION: + tag = CMDTAG_ALTER_SUBSCRIPTION; + break; + case OBJECT_STATISTIC_EXT: + tag = CMDTAG_ALTER_STATISTICS; + break; + default: + tag = CMDTAG_UNKNOWN; + break; + } + + return tag; +} + +/* + * CreateCommandTag + * utility to get a CommandTag for the command operation, + * given either a raw (un-analyzed) parsetree, an analyzed Query, + * or a PlannedStmt. + * + * This must handle all command types, but since the vast majority + * of 'em are utility commands, it seems sensible to keep it here. + */ +CommandTag +CreateCommandTag(Node *parsetree) +{ + CommandTag tag; + + switch (nodeTag(parsetree)) + { + /* recurse if we're given a RawStmt */ + case T_RawStmt: + tag = CreateCommandTag(((RawStmt *) parsetree)->stmt); + break; + + /* raw plannable queries */ + case T_InsertStmt: + tag = CMDTAG_INSERT; + break; + + case T_DeleteStmt: + tag = CMDTAG_DELETE; + break; + + case T_UpdateStmt: + tag = CMDTAG_UPDATE; + break; + + case T_SelectStmt: + tag = CMDTAG_SELECT; + break; + + case T_PLAssignStmt: + tag = CMDTAG_SELECT; + break; + + /* utility statements --- same whether raw or cooked */ + case T_TransactionStmt: + { + TransactionStmt *stmt = (TransactionStmt *) parsetree; + + switch (stmt->kind) + { + case TRANS_STMT_BEGIN: + tag = CMDTAG_BEGIN; + break; + + case TRANS_STMT_START: + tag = CMDTAG_START_TRANSACTION; + break; + + case TRANS_STMT_COMMIT: + tag = CMDTAG_COMMIT; + break; + + case TRANS_STMT_ROLLBACK: + case TRANS_STMT_ROLLBACK_TO: + tag = CMDTAG_ROLLBACK; + break; + + case TRANS_STMT_SAVEPOINT: + tag = CMDTAG_SAVEPOINT; + break; + + case TRANS_STMT_RELEASE: + tag = CMDTAG_RELEASE; + break; + + case TRANS_STMT_PREPARE: + tag = CMDTAG_PREPARE_TRANSACTION; + break; + + case TRANS_STMT_COMMIT_PREPARED: + tag = CMDTAG_COMMIT_PREPARED; + break; + + case TRANS_STMT_ROLLBACK_PREPARED: + tag = CMDTAG_ROLLBACK_PREPARED; + break; + + default: + tag = CMDTAG_UNKNOWN; + break; + } + } + break; + + case T_DeclareCursorStmt: + tag = CMDTAG_DECLARE_CURSOR; + break; + + case T_ClosePortalStmt: + { + ClosePortalStmt *stmt = (ClosePortalStmt *) parsetree; + + if (stmt->portalname == NULL) + tag = CMDTAG_CLOSE_CURSOR_ALL; + else + tag = CMDTAG_CLOSE_CURSOR; + } + break; + + case T_FetchStmt: + { + FetchStmt *stmt = (FetchStmt *) parsetree; + + tag = (stmt->ismove) ? CMDTAG_MOVE : CMDTAG_FETCH; + } + break; + + case T_CreateDomainStmt: + tag = CMDTAG_CREATE_DOMAIN; + break; + + case T_CreateSchemaStmt: + tag = CMDTAG_CREATE_SCHEMA; + break; + + case T_CreateStmt: + tag = CMDTAG_CREATE_TABLE; + break; + + case T_CreateTableSpaceStmt: + tag = CMDTAG_CREATE_TABLESPACE; + break; + + case T_DropTableSpaceStmt: + tag = CMDTAG_DROP_TABLESPACE; + break; + + case T_AlterTableSpaceOptionsStmt: + tag = CMDTAG_ALTER_TABLESPACE; + break; + + case T_CreateExtensionStmt: + tag = CMDTAG_CREATE_EXTENSION; + break; + + case T_AlterExtensionStmt: + tag = CMDTAG_ALTER_EXTENSION; + break; + + case T_AlterExtensionContentsStmt: + tag = CMDTAG_ALTER_EXTENSION; + break; + + case T_CreateFdwStmt: + tag = CMDTAG_CREATE_FOREIGN_DATA_WRAPPER; + break; + + case T_AlterFdwStmt: + tag = CMDTAG_ALTER_FOREIGN_DATA_WRAPPER; + break; + + case T_CreateForeignServerStmt: + tag = CMDTAG_CREATE_SERVER; + break; + + case T_AlterForeignServerStmt: + tag = CMDTAG_ALTER_SERVER; + break; + + case T_CreateUserMappingStmt: + tag = CMDTAG_CREATE_USER_MAPPING; + break; + + case T_AlterUserMappingStmt: + tag = CMDTAG_ALTER_USER_MAPPING; + break; + + case T_DropUserMappingStmt: + tag = CMDTAG_DROP_USER_MAPPING; + break; + + case T_CreateForeignTableStmt: + tag = CMDTAG_CREATE_FOREIGN_TABLE; + break; + + case T_ImportForeignSchemaStmt: + tag = CMDTAG_IMPORT_FOREIGN_SCHEMA; + break; + + case T_DropStmt: + switch (((DropStmt *) parsetree)->removeType) + { + case OBJECT_TABLE: + tag = CMDTAG_DROP_TABLE; + break; + case OBJECT_SEQUENCE: + tag = CMDTAG_DROP_SEQUENCE; + break; + case OBJECT_VIEW: + tag = CMDTAG_DROP_VIEW; + break; + case OBJECT_MATVIEW: + tag = CMDTAG_DROP_MATERIALIZED_VIEW; + break; + case OBJECT_INDEX: + tag = CMDTAG_DROP_INDEX; + break; + case OBJECT_TYPE: + tag = CMDTAG_DROP_TYPE; + break; + case OBJECT_DOMAIN: + tag = CMDTAG_DROP_DOMAIN; + break; + case OBJECT_COLLATION: + tag = CMDTAG_DROP_COLLATION; + break; + case OBJECT_CONVERSION: + tag = CMDTAG_DROP_CONVERSION; + break; + case OBJECT_SCHEMA: + tag = CMDTAG_DROP_SCHEMA; + break; + case OBJECT_TSPARSER: + tag = CMDTAG_DROP_TEXT_SEARCH_PARSER; + break; + case OBJECT_TSDICTIONARY: + tag = CMDTAG_DROP_TEXT_SEARCH_DICTIONARY; + break; + case OBJECT_TSTEMPLATE: + tag = CMDTAG_DROP_TEXT_SEARCH_TEMPLATE; + break; + case OBJECT_TSCONFIGURATION: + tag = CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION; + break; + case OBJECT_FOREIGN_TABLE: + tag = CMDTAG_DROP_FOREIGN_TABLE; + break; + case OBJECT_EXTENSION: + tag = CMDTAG_DROP_EXTENSION; + break; + case OBJECT_FUNCTION: + tag = CMDTAG_DROP_FUNCTION; + break; + case OBJECT_PROCEDURE: + tag = CMDTAG_DROP_PROCEDURE; + break; + case OBJECT_ROUTINE: + tag = CMDTAG_DROP_ROUTINE; + break; + case OBJECT_AGGREGATE: + tag = CMDTAG_DROP_AGGREGATE; + break; + case OBJECT_OPERATOR: + tag = CMDTAG_DROP_OPERATOR; + break; + case OBJECT_LANGUAGE: + tag = CMDTAG_DROP_LANGUAGE; + break; + case OBJECT_CAST: + tag = CMDTAG_DROP_CAST; + break; + case OBJECT_TRIGGER: + tag = CMDTAG_DROP_TRIGGER; + break; + case OBJECT_EVENT_TRIGGER: + tag = CMDTAG_DROP_EVENT_TRIGGER; + break; + case OBJECT_RULE: + tag = CMDTAG_DROP_RULE; + break; + case OBJECT_FDW: + tag = CMDTAG_DROP_FOREIGN_DATA_WRAPPER; + break; + case OBJECT_FOREIGN_SERVER: + tag = CMDTAG_DROP_SERVER; + break; + case OBJECT_OPCLASS: + tag = CMDTAG_DROP_OPERATOR_CLASS; + break; + case OBJECT_OPFAMILY: + tag = CMDTAG_DROP_OPERATOR_FAMILY; + break; + case OBJECT_POLICY: + tag = CMDTAG_DROP_POLICY; + break; + case OBJECT_TRANSFORM: + tag = CMDTAG_DROP_TRANSFORM; + break; + case OBJECT_ACCESS_METHOD: + tag = CMDTAG_DROP_ACCESS_METHOD; + break; + case OBJECT_PUBLICATION: + tag = CMDTAG_DROP_PUBLICATION; + break; + case OBJECT_STATISTIC_EXT: + tag = CMDTAG_DROP_STATISTICS; + break; + default: + tag = CMDTAG_UNKNOWN; + } + break; + + case T_TruncateStmt: + tag = CMDTAG_TRUNCATE_TABLE; + break; + + case T_CommentStmt: + tag = CMDTAG_COMMENT; + break; + + case T_SecLabelStmt: + tag = CMDTAG_SECURITY_LABEL; + break; + + case T_CopyStmt: + tag = CMDTAG_COPY; + break; + + case T_RenameStmt: + + /* + * When the column is renamed, the command tag is created from its + * relation type + */ + tag = AlterObjectTypeCommandTag(((RenameStmt *) parsetree)->renameType == OBJECT_COLUMN ? + ((RenameStmt *) parsetree)->relationType : + ((RenameStmt *) parsetree)->renameType); + break; + + case T_AlterObjectDependsStmt: + tag = AlterObjectTypeCommandTag(((AlterObjectDependsStmt *) parsetree)->objectType); + break; + + case T_AlterObjectSchemaStmt: + tag = AlterObjectTypeCommandTag(((AlterObjectSchemaStmt *) parsetree)->objectType); + break; + + case T_AlterOwnerStmt: + tag = AlterObjectTypeCommandTag(((AlterOwnerStmt *) parsetree)->objectType); + break; + + case T_AlterTableMoveAllStmt: + tag = AlterObjectTypeCommandTag(((AlterTableMoveAllStmt *) parsetree)->objtype); + break; + + case T_AlterTableStmt: + tag = AlterObjectTypeCommandTag(((AlterTableStmt *) parsetree)->objtype); + break; + + case T_AlterDomainStmt: + tag = CMDTAG_ALTER_DOMAIN; + break; + + case T_AlterFunctionStmt: + switch (((AlterFunctionStmt *) parsetree)->objtype) + { + case OBJECT_FUNCTION: + tag = CMDTAG_ALTER_FUNCTION; + break; + case OBJECT_PROCEDURE: + tag = CMDTAG_ALTER_PROCEDURE; + break; + case OBJECT_ROUTINE: + tag = CMDTAG_ALTER_ROUTINE; + break; + default: + tag = CMDTAG_UNKNOWN; + } + break; + + case T_GrantStmt: + { + GrantStmt *stmt = (GrantStmt *) parsetree; + + tag = (stmt->is_grant) ? CMDTAG_GRANT : CMDTAG_REVOKE; + } + break; + + case T_GrantRoleStmt: + { + GrantRoleStmt *stmt = (GrantRoleStmt *) parsetree; + + tag = (stmt->is_grant) ? CMDTAG_GRANT_ROLE : CMDTAG_REVOKE_ROLE; + } + break; + + case T_AlterDefaultPrivilegesStmt: + tag = CMDTAG_ALTER_DEFAULT_PRIVILEGES; + break; + + case T_DefineStmt: + switch (((DefineStmt *) parsetree)->kind) + { + case OBJECT_AGGREGATE: + tag = CMDTAG_CREATE_AGGREGATE; + break; + case OBJECT_OPERATOR: + tag = CMDTAG_CREATE_OPERATOR; + break; + case OBJECT_TYPE: + tag = CMDTAG_CREATE_TYPE; + break; + case OBJECT_TSPARSER: + tag = CMDTAG_CREATE_TEXT_SEARCH_PARSER; + break; + case OBJECT_TSDICTIONARY: + tag = CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY; + break; + case OBJECT_TSTEMPLATE: + tag = CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE; + break; + case OBJECT_TSCONFIGURATION: + tag = CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION; + break; + case OBJECT_COLLATION: + tag = CMDTAG_CREATE_COLLATION; + break; + case OBJECT_ACCESS_METHOD: + tag = CMDTAG_CREATE_ACCESS_METHOD; + break; + default: + tag = CMDTAG_UNKNOWN; + } + break; + + case T_CompositeTypeStmt: + tag = CMDTAG_CREATE_TYPE; + break; + + case T_CreateEnumStmt: + tag = CMDTAG_CREATE_TYPE; + break; + + case T_CreateRangeStmt: + tag = CMDTAG_CREATE_TYPE; + break; + + case T_AlterEnumStmt: + tag = CMDTAG_ALTER_TYPE; + break; + + case T_ViewStmt: + tag = CMDTAG_CREATE_VIEW; + break; + + case T_CreateFunctionStmt: + if (((CreateFunctionStmt *) parsetree)->is_procedure) + tag = CMDTAG_CREATE_PROCEDURE; + else + tag = CMDTAG_CREATE_FUNCTION; + break; + + case T_IndexStmt: + tag = CMDTAG_CREATE_INDEX; + break; + + case T_RuleStmt: + tag = CMDTAG_CREATE_RULE; + break; + + case T_CreateSeqStmt: + tag = CMDTAG_CREATE_SEQUENCE; + break; + + case T_AlterSeqStmt: + tag = CMDTAG_ALTER_SEQUENCE; + break; + + case T_DoStmt: + tag = CMDTAG_DO; + break; + + case T_CreatedbStmt: + tag = CMDTAG_CREATE_DATABASE; + break; + + case T_AlterDatabaseStmt: + tag = CMDTAG_ALTER_DATABASE; + break; + + case T_AlterDatabaseSetStmt: + tag = CMDTAG_ALTER_DATABASE; + break; + + case T_DropdbStmt: + tag = CMDTAG_DROP_DATABASE; + break; + + case T_NotifyStmt: + tag = CMDTAG_NOTIFY; + break; + + case T_ListenStmt: + tag = CMDTAG_LISTEN; + break; + + case T_UnlistenStmt: + tag = CMDTAG_UNLISTEN; + break; + + case T_LoadStmt: + tag = CMDTAG_LOAD; + break; + + case T_CallStmt: + tag = CMDTAG_CALL; + break; + + case T_ClusterStmt: + tag = CMDTAG_CLUSTER; + break; + + case T_VacuumStmt: + if (((VacuumStmt *) parsetree)->is_vacuumcmd) + tag = CMDTAG_VACUUM; + else + tag = CMDTAG_ANALYZE; + break; + + case T_ExplainStmt: + tag = CMDTAG_EXPLAIN; + break; + + case T_CreateTableAsStmt: + switch (((CreateTableAsStmt *) parsetree)->objtype) + { + case OBJECT_TABLE: + if (((CreateTableAsStmt *) parsetree)->is_select_into) + tag = CMDTAG_SELECT_INTO; + else + tag = CMDTAG_CREATE_TABLE_AS; + break; + case OBJECT_MATVIEW: + tag = CMDTAG_CREATE_MATERIALIZED_VIEW; + break; + default: + tag = CMDTAG_UNKNOWN; + } + break; + + case T_RefreshMatViewStmt: + tag = CMDTAG_REFRESH_MATERIALIZED_VIEW; + break; + + case T_AlterSystemStmt: + tag = CMDTAG_ALTER_SYSTEM; + break; + + case T_VariableSetStmt: + switch (((VariableSetStmt *) parsetree)->kind) + { + case VAR_SET_VALUE: + case VAR_SET_CURRENT: + case VAR_SET_DEFAULT: + case VAR_SET_MULTI: + tag = CMDTAG_SET; + break; + case VAR_RESET: + case VAR_RESET_ALL: + tag = CMDTAG_RESET; + break; + default: + tag = CMDTAG_UNKNOWN; + } + break; + + case T_VariableShowStmt: + tag = CMDTAG_SHOW; + break; + + case T_DiscardStmt: + switch (((DiscardStmt *) parsetree)->target) + { + case DISCARD_ALL: + tag = CMDTAG_DISCARD_ALL; + break; + case DISCARD_PLANS: + tag = CMDTAG_DISCARD_PLANS; + break; + case DISCARD_TEMP: + tag = CMDTAG_DISCARD_TEMP; + break; + case DISCARD_SEQUENCES: + tag = CMDTAG_DISCARD_SEQUENCES; + break; + default: + tag = CMDTAG_UNKNOWN; + } + break; + + case T_CreateTransformStmt: + tag = CMDTAG_CREATE_TRANSFORM; + break; + + case T_CreateTrigStmt: + tag = CMDTAG_CREATE_TRIGGER; + break; + + case T_CreateEventTrigStmt: + tag = CMDTAG_CREATE_EVENT_TRIGGER; + break; + + case T_AlterEventTrigStmt: + tag = CMDTAG_ALTER_EVENT_TRIGGER; + break; + + case T_CreatePLangStmt: + tag = CMDTAG_CREATE_LANGUAGE; + break; + + case T_CreateRoleStmt: + tag = CMDTAG_CREATE_ROLE; + break; + + case T_AlterRoleStmt: + tag = CMDTAG_ALTER_ROLE; + break; + + case T_AlterRoleSetStmt: + tag = CMDTAG_ALTER_ROLE; + break; + + case T_DropRoleStmt: + tag = CMDTAG_DROP_ROLE; + break; + + case T_DropOwnedStmt: + tag = CMDTAG_DROP_OWNED; + break; + + case T_ReassignOwnedStmt: + tag = CMDTAG_REASSIGN_OWNED; + break; + + case T_LockStmt: + tag = CMDTAG_LOCK_TABLE; + break; + + case T_ConstraintsSetStmt: + tag = CMDTAG_SET_CONSTRAINTS; + break; + + case T_CheckPointStmt: + tag = CMDTAG_CHECKPOINT; + break; + + case T_ReindexStmt: + tag = CMDTAG_REINDEX; + break; + + case T_CreateConversionStmt: + tag = CMDTAG_CREATE_CONVERSION; + break; + + case T_CreateCastStmt: + tag = CMDTAG_CREATE_CAST; + break; + + case T_CreateOpClassStmt: + tag = CMDTAG_CREATE_OPERATOR_CLASS; + break; + + case T_CreateOpFamilyStmt: + tag = CMDTAG_CREATE_OPERATOR_FAMILY; + break; + + case T_AlterOpFamilyStmt: + tag = CMDTAG_ALTER_OPERATOR_FAMILY; + break; + + case T_AlterOperatorStmt: + tag = CMDTAG_ALTER_OPERATOR; + break; + + case T_AlterTypeStmt: + tag = CMDTAG_ALTER_TYPE; + break; + + case T_AlterTSDictionaryStmt: + tag = CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY; + break; + + case T_AlterTSConfigurationStmt: + tag = CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION; + break; + + case T_CreatePolicyStmt: + tag = CMDTAG_CREATE_POLICY; + break; + + case T_AlterPolicyStmt: + tag = CMDTAG_ALTER_POLICY; + break; + + case T_CreateAmStmt: + tag = CMDTAG_CREATE_ACCESS_METHOD; + break; + + case T_CreatePublicationStmt: + tag = CMDTAG_CREATE_PUBLICATION; + break; + + case T_AlterPublicationStmt: + tag = CMDTAG_ALTER_PUBLICATION; + break; + + case T_CreateSubscriptionStmt: + tag = CMDTAG_CREATE_SUBSCRIPTION; + break; + + case T_AlterSubscriptionStmt: + tag = CMDTAG_ALTER_SUBSCRIPTION; + break; + + case T_DropSubscriptionStmt: + tag = CMDTAG_DROP_SUBSCRIPTION; + break; + + case T_AlterCollationStmt: + tag = CMDTAG_ALTER_COLLATION; + break; + + case T_PrepareStmt: + tag = CMDTAG_PREPARE; + break; + + case T_ExecuteStmt: + tag = CMDTAG_EXECUTE; + break; + + case T_CreateStatsStmt: + tag = CMDTAG_CREATE_STATISTICS; + break; + + case T_AlterStatsStmt: + tag = CMDTAG_ALTER_STATISTICS; + break; + + case T_DeallocateStmt: + { + DeallocateStmt *stmt = (DeallocateStmt *) parsetree; + + if (stmt->name == NULL) + tag = CMDTAG_DEALLOCATE_ALL; + else + tag = CMDTAG_DEALLOCATE; + } + break; + + /* already-planned queries */ + case T_PlannedStmt: + { + PlannedStmt *stmt = (PlannedStmt *) parsetree; + + switch (stmt->commandType) + { + case CMD_SELECT: + + /* + * We take a little extra care here so that the result + * will be useful for complaints about read-only + * statements + */ + if (stmt->rowMarks != NIL) + { + /* not 100% but probably close enough */ + switch (((PlanRowMark *) linitial(stmt->rowMarks))->strength) + { + case LCS_FORKEYSHARE: + tag = CMDTAG_SELECT_FOR_KEY_SHARE; + break; + case LCS_FORSHARE: + tag = CMDTAG_SELECT_FOR_SHARE; + break; + case LCS_FORNOKEYUPDATE: + tag = CMDTAG_SELECT_FOR_NO_KEY_UPDATE; + break; + case LCS_FORUPDATE: + tag = CMDTAG_SELECT_FOR_UPDATE; + break; + default: + tag = CMDTAG_SELECT; + break; + } + } + else + tag = CMDTAG_SELECT; + break; + case CMD_UPDATE: + tag = CMDTAG_UPDATE; + break; + case CMD_INSERT: + tag = CMDTAG_INSERT; + break; + case CMD_DELETE: + tag = CMDTAG_DELETE; + break; + case CMD_UTILITY: + tag = CreateCommandTag(stmt->utilityStmt); + break; + default: + elog(WARNING, "unrecognized commandType: %d", + (int) stmt->commandType); + tag = CMDTAG_UNKNOWN; + break; + } + } + break; + + /* parsed-and-rewritten-but-not-planned queries */ + case T_Query: + { + Query *stmt = (Query *) parsetree; + + switch (stmt->commandType) + { + case CMD_SELECT: + + /* + * We take a little extra care here so that the result + * will be useful for complaints about read-only + * statements + */ + if (stmt->rowMarks != NIL) + { + /* not 100% but probably close enough */ + switch (((RowMarkClause *) linitial(stmt->rowMarks))->strength) + { + case LCS_FORKEYSHARE: + tag = CMDTAG_SELECT_FOR_KEY_SHARE; + break; + case LCS_FORSHARE: + tag = CMDTAG_SELECT_FOR_SHARE; + break; + case LCS_FORNOKEYUPDATE: + tag = CMDTAG_SELECT_FOR_NO_KEY_UPDATE; + break; + case LCS_FORUPDATE: + tag = CMDTAG_SELECT_FOR_UPDATE; + break; + default: + tag = CMDTAG_UNKNOWN; + break; + } + } + else + tag = CMDTAG_SELECT; + break; + case CMD_UPDATE: + tag = CMDTAG_UPDATE; + break; + case CMD_INSERT: + tag = CMDTAG_INSERT; + break; + case CMD_DELETE: + tag = CMDTAG_DELETE; + break; + case CMD_UTILITY: + tag = CreateCommandTag(stmt->utilityStmt); + break; + default: + elog(WARNING, "unrecognized commandType: %d", + (int) stmt->commandType); + tag = CMDTAG_UNKNOWN; + break; + } + } + break; + + default: + elog(WARNING, "unrecognized node type: %d", + (int) nodeTag(parsetree)); + tag = CMDTAG_UNKNOWN; + break; + } + + return tag; +} + + +/* + * GetCommandLogLevel + * utility to get the minimum log_statement level for a command, + * given either a raw (un-analyzed) parsetree, an analyzed Query, + * or a PlannedStmt. + * + * This must handle all command types, but since the vast majority + * of 'em are utility commands, it seems sensible to keep it here. + */ +LogStmtLevel +GetCommandLogLevel(Node *parsetree) +{ + LogStmtLevel lev; + + switch (nodeTag(parsetree)) + { + /* recurse if we're given a RawStmt */ + case T_RawStmt: + lev = GetCommandLogLevel(((RawStmt *) parsetree)->stmt); + break; + + /* raw plannable queries */ + case T_InsertStmt: + case T_DeleteStmt: + case T_UpdateStmt: + lev = LOGSTMT_MOD; + break; + + case T_SelectStmt: + if (((SelectStmt *) parsetree)->intoClause) + lev = LOGSTMT_DDL; /* SELECT INTO */ + else + lev = LOGSTMT_ALL; + break; + + case T_PLAssignStmt: + lev = LOGSTMT_ALL; + break; + + /* utility statements --- same whether raw or cooked */ + case T_TransactionStmt: + lev = LOGSTMT_ALL; + break; + + case T_DeclareCursorStmt: + lev = LOGSTMT_ALL; + break; + + case T_ClosePortalStmt: + lev = LOGSTMT_ALL; + break; + + case T_FetchStmt: + lev = LOGSTMT_ALL; + break; + + case T_CreateSchemaStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateStmt: + case T_CreateForeignTableStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateTableSpaceStmt: + case T_DropTableSpaceStmt: + case T_AlterTableSpaceOptionsStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateExtensionStmt: + case T_AlterExtensionStmt: + case T_AlterExtensionContentsStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateFdwStmt: + case T_AlterFdwStmt: + case T_CreateForeignServerStmt: + case T_AlterForeignServerStmt: + case T_CreateUserMappingStmt: + case T_AlterUserMappingStmt: + case T_DropUserMappingStmt: + case T_ImportForeignSchemaStmt: + lev = LOGSTMT_DDL; + break; + + case T_DropStmt: + lev = LOGSTMT_DDL; + break; + + case T_TruncateStmt: + lev = LOGSTMT_MOD; + break; + + case T_CommentStmt: + lev = LOGSTMT_DDL; + break; + + case T_SecLabelStmt: + lev = LOGSTMT_DDL; + break; + + case T_CopyStmt: + if (((CopyStmt *) parsetree)->is_from) + lev = LOGSTMT_MOD; + else + lev = LOGSTMT_ALL; + break; + + case T_PrepareStmt: + { + PrepareStmt *stmt = (PrepareStmt *) parsetree; + + /* Look through a PREPARE to the contained stmt */ + lev = GetCommandLogLevel(stmt->query); + } + break; + + case T_ExecuteStmt: + { + ExecuteStmt *stmt = (ExecuteStmt *) parsetree; + PreparedStatement *ps; + + /* Look through an EXECUTE to the referenced stmt */ + ps = FetchPreparedStatement(stmt->name, false); + if (ps && ps->plansource->raw_parse_tree) + lev = GetCommandLogLevel(ps->plansource->raw_parse_tree->stmt); + else + lev = LOGSTMT_ALL; + } + break; + + case T_DeallocateStmt: + lev = LOGSTMT_ALL; + break; + + case T_RenameStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterObjectDependsStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterObjectSchemaStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterOwnerStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterOperatorStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterTypeStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterTableMoveAllStmt: + case T_AlterTableStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterDomainStmt: + lev = LOGSTMT_DDL; + break; + + case T_GrantStmt: + lev = LOGSTMT_DDL; + break; + + case T_GrantRoleStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterDefaultPrivilegesStmt: + lev = LOGSTMT_DDL; + break; + + case T_DefineStmt: + lev = LOGSTMT_DDL; + break; + + case T_CompositeTypeStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateEnumStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateRangeStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterEnumStmt: + lev = LOGSTMT_DDL; + break; + + case T_ViewStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateFunctionStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterFunctionStmt: + lev = LOGSTMT_DDL; + break; + + case T_IndexStmt: + lev = LOGSTMT_DDL; + break; + + case T_RuleStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateSeqStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterSeqStmt: + lev = LOGSTMT_DDL; + break; + + case T_DoStmt: + lev = LOGSTMT_ALL; + break; + + case T_CreatedbStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterDatabaseStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterDatabaseSetStmt: + lev = LOGSTMT_DDL; + break; + + case T_DropdbStmt: + lev = LOGSTMT_DDL; + break; + + case T_NotifyStmt: + lev = LOGSTMT_ALL; + break; + + case T_ListenStmt: + lev = LOGSTMT_ALL; + break; + + case T_UnlistenStmt: + lev = LOGSTMT_ALL; + break; + + case T_LoadStmt: + lev = LOGSTMT_ALL; + break; + + case T_CallStmt: + lev = LOGSTMT_ALL; + break; + + case T_ClusterStmt: + lev = LOGSTMT_DDL; + break; + + case T_VacuumStmt: + lev = LOGSTMT_ALL; + break; + + case T_ExplainStmt: + { + ExplainStmt *stmt = (ExplainStmt *) parsetree; + bool analyze = false; + ListCell *lc; + + /* Look through an EXPLAIN ANALYZE to the contained stmt */ + foreach(lc, stmt->options) + { + DefElem *opt = (DefElem *) lfirst(lc); + + if (strcmp(opt->defname, "analyze") == 0) + analyze = defGetBoolean(opt); + /* don't "break", as explain.c will use the last value */ + } + if (analyze) + return GetCommandLogLevel(stmt->query); + + /* Plain EXPLAIN isn't so interesting */ + lev = LOGSTMT_ALL; + } + break; + + case T_CreateTableAsStmt: + lev = LOGSTMT_DDL; + break; + + case T_RefreshMatViewStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterSystemStmt: + lev = LOGSTMT_DDL; + break; + + case T_VariableSetStmt: + lev = LOGSTMT_ALL; + break; + + case T_VariableShowStmt: + lev = LOGSTMT_ALL; + break; + + case T_DiscardStmt: + lev = LOGSTMT_ALL; + break; + + case T_CreateTrigStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateEventTrigStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterEventTrigStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreatePLangStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateDomainStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateRoleStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterRoleStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterRoleSetStmt: + lev = LOGSTMT_DDL; + break; + + case T_DropRoleStmt: + lev = LOGSTMT_DDL; + break; + + case T_DropOwnedStmt: + lev = LOGSTMT_DDL; + break; + + case T_ReassignOwnedStmt: + lev = LOGSTMT_DDL; + break; + + case T_LockStmt: + lev = LOGSTMT_ALL; + break; + + case T_ConstraintsSetStmt: + lev = LOGSTMT_ALL; + break; + + case T_CheckPointStmt: + lev = LOGSTMT_ALL; + break; + + case T_ReindexStmt: + lev = LOGSTMT_ALL; /* should this be DDL? */ + break; + + case T_CreateConversionStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateCastStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateOpClassStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateOpFamilyStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateTransformStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterOpFamilyStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreatePolicyStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterPolicyStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterTSDictionaryStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterTSConfigurationStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateAmStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreatePublicationStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterPublicationStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateSubscriptionStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterSubscriptionStmt: + lev = LOGSTMT_DDL; + break; + + case T_DropSubscriptionStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateStatsStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterStatsStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterCollationStmt: + lev = LOGSTMT_DDL; + break; + + /* already-planned queries */ + case T_PlannedStmt: + { + PlannedStmt *stmt = (PlannedStmt *) parsetree; + + switch (stmt->commandType) + { + case CMD_SELECT: + lev = LOGSTMT_ALL; + break; + + case CMD_UPDATE: + case CMD_INSERT: + case CMD_DELETE: + lev = LOGSTMT_MOD; + break; + + case CMD_UTILITY: + lev = GetCommandLogLevel(stmt->utilityStmt); + break; + + default: + elog(WARNING, "unrecognized commandType: %d", + (int) stmt->commandType); + lev = LOGSTMT_ALL; + break; + } + } + break; + + /* parsed-and-rewritten-but-not-planned queries */ + case T_Query: + { + Query *stmt = (Query *) parsetree; + + switch (stmt->commandType) + { + case CMD_SELECT: + lev = LOGSTMT_ALL; + break; + + case CMD_UPDATE: + case CMD_INSERT: + case CMD_DELETE: + lev = LOGSTMT_MOD; + break; + + case CMD_UTILITY: + lev = GetCommandLogLevel(stmt->utilityStmt); + break; + + default: + elog(WARNING, "unrecognized commandType: %d", + (int) stmt->commandType); + lev = LOGSTMT_ALL; + break; + } + + } + break; + + default: + elog(WARNING, "unrecognized node type: %d", + (int) nodeTag(parsetree)); + lev = LOGSTMT_ALL; + break; + } + + return lev; +} |