| /*------------------------------------------------------------------------- |
| * |
| * 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-2023, 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_authid.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/dirtablecmds.h" |
| #include "commands/discard.h" |
| #include "commands/event_trigger.h" |
| #include "commands/explain.h" |
| #include "commands/extension.h" |
| #include "commands/extprotocolcmds.h" |
| #include "commands/lockcmds.h" |
| #include "commands/matview.h" |
| #include "commands/policy.h" |
| #include "commands/portalcmds.h" |
| #include "commands/prepare.h" |
| #include "commands/queue.h" |
| #include "commands/proclang.h" |
| #include "commands/publicationcmds.h" |
| #include "commands/resgroupcmds.h" |
| #include "commands/schemacmds.h" |
| #include "commands/seclabel.h" |
| #include "commands/sequence.h" |
| #include "commands/storagecmds.h" |
| #include "commands/subscriptioncmds.h" |
| #include "commands/tablecmds.h" |
| #include "commands/tablespace.h" |
| #include "commands/tag.h" |
| #include "commands/taskcmds.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" |
| |
| #include "access/table.h" |
| #include "catalog/oid_dispatch.h" |
| #include "catalog/pg_profile.h" |
| #include "cdb/cdbdisp_query.h" |
| #include "cdb/cdbendpoint.h" |
| #include "cdb/cdbvars.h" |
| |
| |
| /* Hook for plugins to get control in ProcessUtility() */ |
| ProcessUtility_hook_type ProcessUtility_hook = NULL; |
| |
| /* counter to disable dispatch */ |
| int dispatch_nest_level = 0; |
| |
| /* |
| * Greenplumn specific code: |
| * for detailed comments, please refer to the comments at the |
| * definition of executor_run_nesting_level in execMain.c. |
| * Greenplum now support create procedure, so auto_stats also |
| * need to take inside a procedure as inside function call. |
| * process_utility_nesting_level >= 2 implies in function call |
| * when calling from procedure. |
| */ |
| static int process_utility_nesting_level = 0; |
| |
| /* 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: |
| case CMD_MERGE: |
| 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_AlterDatabaseRefreshCollStmt: |
| 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_AlterSchemaStmt: |
| 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_AlterStorageUserMappingStmt: |
| 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_AddForeignSegStmt: |
| 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_CreateStorageServerStmt: |
| case T_AlterStorageServerStmt: |
| case T_DropStorageServerStmt: |
| case T_CreateUserMappingStmt: |
| case T_CreateStorageUserMappingStmt: |
| 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_DropStorageUserMappingStmt: |
| 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: |
| /* fallthrough */ |
| /* GPDB specific commands */ |
| case T_AlterProfileStmt: |
| case T_AlterQueueStmt: |
| case T_AlterResourceGroupStmt: |
| case T_AlterTagStmt: |
| case T_CreateDirectoryTableStmt: |
| case T_AlterDirectoryTableStmt: |
| case T_DropDirectoryTableStmt: |
| case T_CreateProfileStmt: |
| case T_CreateQueueStmt: |
| case T_CreateResourceGroupStmt: |
| case T_CreateTaskStmt: |
| case T_CreateTagStmt: |
| case T_AlterTaskStmt: |
| case T_DropTaskStmt: |
| case T_DropProfileStmt: |
| case T_DropQueueStmt: |
| case T_DropResourceGroupStmt: |
| case T_DropTagStmt: |
| case T_DropWarehouseStmt: |
| case T_CreateExternalStmt: |
| case T_RetrieveStmt: |
| case T_CreateWarehouseStmt: |
| { |
| /* 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))); |
| |
| /* |
| * This is also a convenient place to make note that this transaction |
| * needs two-phase commit. |
| */ |
| ExecutorMarkTransactionDoesWrites(); |
| } |
| |
| /* |
| * 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); |
| |
| /* |
| * Greenplum specific code: |
| * Please refer to the comments at the definition of process_utility_nesting_level. |
| */ |
| process_utility_nesting_level++; |
| PG_TRY(); |
| { |
| /* |
| * 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); |
| process_utility_nesting_level--; |
| } |
| PG_CATCH(); |
| { |
| process_utility_nesting_level--; |
| PG_RE_THROW(); |
| } |
| PG_END_TRY(); |
| } |
| |
| /* |
| * 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) |
| SetPGVariableOptDispatch("transaction_isolation", |
| list_make1(item->arg), |
| true, |
| /* gp_dispatch */ false); |
| else if (strcmp(item->defname, "transaction_read_only") == 0) |
| SetPGVariableOptDispatch("transaction_read_only", |
| list_make1(item->arg), |
| true, |
| /* gp_dispatch */ false); |
| else if (strcmp(item->defname, "transaction_deferrable") == 0) |
| SetPGVariableOptDispatch("transaction_deferrable", |
| list_make1(item->arg), |
| true, |
| /* gp_dispatch */ false); |
| } |
| |
| sendDtxExplicitBegin(); |
| } |
| 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: |
| /* |
| * SINGLENODE_FIXME: |
| * It seems like `PREPARE TRANSACTION` works in singlenode mode, |
| * but currently we just disable it to make it behaves closer |
| * to cluster mode. |
| * Maybe reconsider it in the future. |
| */ |
| if (Gp_role == GP_ROLE_DISPATCH || IS_SINGLENODE()) |
| { |
| ereport(ERROR, (errcode(ERRCODE_GP_COMMAND_ERROR), |
| errmsg("PREPARE TRANSACTION is not yet supported in Apache Cloudberry"))); |
| |
| } |
| PreventCommandDuringRecovery("PREPARE TRANSACTION"); |
| if (!PrepareTransactionBlock(stmt->gid)) |
| { |
| /* report unsuccessful commit in qc */ |
| if (qc) |
| SetQueryCompletion(qc, CMDTAG_ROLLBACK, 0); |
| } |
| break; |
| |
| case TRANS_STMT_COMMIT_PREPARED: |
| if (Gp_role == GP_ROLE_DISPATCH) |
| { |
| ereport(ERROR, (errcode(ERRCODE_GP_COMMAND_ERROR), |
| errmsg("COMMIT PREPARED is not yet supported in Apache Cloudberry"))); |
| } |
| PreventInTransactionBlock(isTopLevel, "COMMIT PREPARED"); |
| PreventCommandDuringRecovery("COMMIT PREPARED"); |
| FinishPreparedTransaction(stmt->gid, /* isCommit */ true, /* raiseErrorIfNotFound */ true); |
| break; |
| |
| case TRANS_STMT_ROLLBACK_PREPARED: |
| if (Gp_role == GP_ROLE_DISPATCH) |
| { |
| ereport(ERROR, (errcode(ERRCODE_GP_COMMAND_ERROR), |
| errmsg("ROLLBACK PREPARED is not yet supported in Apache Cloudberry"))); |
| } |
| PreventInTransactionBlock(isTopLevel, "ROLLBACK PREPARED"); |
| PreventCommandDuringRecovery("ROLLBACK PREPARED"); |
| FinishPreparedTransaction(stmt->gid, /* isCommit */ false, /* raiseErrorIfNotFound */ true); |
| break; |
| |
| case TRANS_STMT_ROLLBACK: |
| UserAbortTransactionBlock(stmt->chain); |
| break; |
| |
| case TRANS_STMT_SAVEPOINT: |
| RequireTransactionBlock(isTopLevel, "SAVEPOINT"); |
| /* We already checked that we're in a |
| * transaction; need to make certain |
| * that the BEGIN has been dispatched |
| * before we start dispatching our savepoint. |
| */ |
| sendDtxExplicitBegin(); |
| |
| DefineDispatchSavepoint(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(pstate, (DoStmt *) parsetree, isAtomicContext); |
| break; |
| |
| case T_CreateTableSpaceStmt: |
| /* no event triggers for global objects */ |
| if (Gp_role != GP_ROLE_EXECUTE) |
| { |
| /* |
| * Don't allow master to call this in a transaction block. Segments |
| * are ok as distributed transaction participants. |
| */ |
| PreventInTransactionBlock(isTopLevel, "CREATE TABLESPACE"); |
| } |
| CreateTableSpace((CreateTableSpaceStmt *) parsetree); |
| break; |
| |
| case T_DropTableSpaceStmt: |
| /* no event triggers for global objects */ |
| if (Gp_role != GP_ROLE_EXECUTE) |
| { |
| /* |
| * Don't allow master to call this in a transaction block. Segments are ok as |
| * distributed transaction participants. |
| */ |
| 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(pstate, (GrantRoleStmt *) parsetree); |
| break; |
| |
| case T_CreatedbStmt: |
| /* no event triggers for global objects */ |
| if (Gp_role != GP_ROLE_EXECUTE) |
| { |
| /* |
| * Don't allow master to call this in a transaction block. Segments |
| * are ok as distributed transaction participants. |
| */ |
| 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_AlterDatabaseRefreshCollStmt: |
| /* no event triggers for global objects */ |
| AlterDatabaseRefreshColl((AlterDatabaseRefreshCollStmt *) parsetree); |
| break; |
| |
| case T_AlterDatabaseSetStmt: |
| /* no event triggers for global objects */ |
| AlterDatabaseSet((AlterDatabaseSetStmt *) parsetree); |
| break; |
| |
| case T_DropdbStmt: |
| /* no event triggers for global objects */ |
| if (Gp_role != GP_ROLE_EXECUTE) |
| { |
| /* |
| * Don't allow master to call this in a transaction block. Segments |
| * are ok as distributed transaction participants. |
| */ |
| PreventInTransactionBlock(isTopLevel, "DROP DATABASE"); |
| } |
| DropDatabase(pstate, (DropdbStmt *) parsetree); |
| break; |
| |
| /* Query-level asynchronous notification */ |
| case T_NotifyStmt: |
| { |
| NotifyStmt *stmt = (NotifyStmt *) parsetree; |
| |
| if (Gp_role == GP_ROLE_EXECUTE) |
| ereport(ERROR, (errcode(ERRCODE_GP_COMMAND_ERROR), |
| errmsg("notify command cannot run in a function running on a segDB"))); |
| |
| PreventCommandDuringRecovery("NOTIFY"); |
| Async_Notify(stmt->conditionname, stmt->payload); |
| } |
| break; |
| |
| case T_ListenStmt: |
| { |
| ListenStmt *stmt = (ListenStmt *) parsetree; |
| |
| if (Gp_role == GP_ROLE_EXECUTE) |
| ereport(ERROR,(errcode(ERRCODE_GP_COMMAND_ERROR), |
| errmsg("listen command cannot run in a function running on a segDB"))); |
| |
| PreventCommandDuringRecovery("LISTEN"); |
| 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; |
| |
| if (Gp_role == GP_ROLE_EXECUTE) |
| ereport(ERROR, (errcode(ERRCODE_GP_COMMAND_ERROR), |
| errmsg("unlisten command cannot run in a function running on a segDB"))); |
| |
| /* we allow UNLISTEN during recovery, as it's a noop */ |
| 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()); |
| |
| if (Gp_role == GP_ROLE_DISPATCH) |
| { |
| StringInfoData buffer; |
| |
| initStringInfo(&buffer); |
| |
| appendStringInfo(&buffer, "LOAD '%s'", stmt->filename); |
| |
| CdbDispatchCommand(buffer.data, |
| DF_WITH_SNAPSHOT, |
| NULL); |
| pfree(buffer.data); |
| } |
| } |
| break; |
| |
| case T_CallStmt: |
| ExecuteCallStmt(castNode(CallStmt, parsetree), params, isAtomicContext, dest); |
| break; |
| |
| case T_ClusterStmt: |
| cluster(pstate, (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, false); |
| } |
| break; |
| |
| case T_CreateTagStmt: |
| CreateTag((CreateTagStmt *) parsetree); |
| break; |
| |
| case T_AlterTagStmt: |
| AlterTag((AlterTagStmt *) parsetree); |
| break; |
| |
| case T_DropTagStmt: |
| DropTag((DropTagStmt *) parsetree); |
| break; |
| |
| case T_ExplainStmt: |
| ExplainQuery(pstate, (ExplainStmt *) parsetree, params, dest); |
| break; |
| |
| |
| case T_AlterSystemStmt: |
| PreventInTransactionBlock(isTopLevel, "ALTER SYSTEM"); |
| AlterSystemSetConfigFile((AlterSystemStmt *) parsetree); |
| if (Gp_role == GP_ROLE_DISPATCH && ENABLE_DISPATCH()) |
| CdbDispatchUtilityStatement((Node *) parsetree, |
| DF_CANCEL_ON_ERROR, |
| NULL, |
| NULL); |
| 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; |
| |
| /* |
| * ********************* RESOURCE QUEUE statements **** |
| */ |
| case T_CreateQueueStmt: |
| |
| /* |
| * MPP-7960: We cannot run CREATE RESOURCE QUEUE inside a user |
| * transaction block because the shared memory structures are not |
| * cleaned up on abort, resulting in "leaked", unreachable queues. |
| */ |
| /* |
| * SINGLENODE_FIXME: not sure if it's happening in single node, |
| * let's just keep it behaving the same as greenplum for safety. |
| */ |
| if (Gp_role == GP_ROLE_DISPATCH || IS_SINGLENODE()) |
| PreventInTransactionBlock(isTopLevel, "CREATE RESOURCE QUEUE"); |
| |
| CreateQueue((CreateQueueStmt *) parsetree); |
| break; |
| |
| case T_AlterQueueStmt: |
| AlterQueue((AlterQueueStmt *) parsetree); |
| break; |
| |
| case T_DropQueueStmt: |
| DropQueue((DropQueueStmt *) parsetree); |
| break; |
| |
| /* |
| * ********************* RESOURCE GROUP statements **** |
| */ |
| case T_CreateResourceGroupStmt: |
| if (Gp_role == GP_ROLE_DISPATCH) |
| PreventInTransactionBlock(isTopLevel, "CREATE RESOURCE GROUP"); |
| |
| CreateResourceGroup((CreateResourceGroupStmt *) parsetree); |
| break; |
| |
| case T_AlterResourceGroupStmt: |
| if (Gp_role == GP_ROLE_DISPATCH) |
| PreventInTransactionBlock(isTopLevel, "ALTER RESOURCE GROUP"); |
| |
| AlterResourceGroup((AlterResourceGroupStmt *) parsetree); |
| break; |
| |
| case T_DropResourceGroupStmt: |
| if (Gp_role == GP_ROLE_DISPATCH) |
| PreventInTransactionBlock(isTopLevel, "DROP RESOURCE GROUP"); |
| |
| DropResourceGroup((DropResourceGroupStmt *) 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(pstate, (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_CreateProfileStmt: |
| CreateProfile(pstate, (CreateProfileStmt *) parsetree); |
| break; |
| |
| case T_AlterProfileStmt: |
| AlterProfile((AlterProfileStmt *) parsetree); |
| break; |
| |
| case T_DropProfileStmt: |
| DropProfile((DropProfileStmt *) 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 (!has_privs_of_role(GetUserId(), ROLE_PG_CHECKPOINT)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| /* translator: %s is name of a SQL command, eg CHECKPOINT */ |
| errmsg("permission denied to execute %s command", |
| "CHECKPOINT"), |
| errdetail("Only roles with privileges of the \"%s\" role may execute this command.", |
| "pg_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. |
| */ |
| if (Gp_role == GP_ROLE_DISPATCH) |
| { |
| CdbDispatchCommand("CHECKPOINT", 0, NULL); |
| } |
| 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_DropDirectoryTableStmt: |
| { |
| DropStmt *stmt = (DropStmt *) &((DropDirectoryTableStmt *) parsetree)->base; |
| |
| 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_RetrieveStmt: |
| ExecRetrieveStmt((RetrieveStmt *) parsetree, dest); |
| 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_AlterSchemaStmt: |
| AlterSchemaCommand((AlterSchemaStmt *) parsetree); |
| /* |
| * EventTriggerCollectSimpleCommand called by |
| * CreateSchemaCommand |
| */ |
| commandCollected = true; |
| break; |
| |
| case T_CreateStmt: |
| case T_CreateForeignTableStmt: |
| case T_CreateDirectoryTableStmt: |
| { |
| List *stmts; |
| RangeVar *table_rv = NULL; |
| |
| /* Run parse analysis ... */ |
| /* |
| * GPDB: Only do parse analysis in the Query Dispatcher. The Executor |
| * nodes receive an already-transformed statement from the QD. We only |
| * want to process the main CreateStmt here, not any auxiliary IndexStmts |
| * or other such statements that would be created from the main |
| * CreateStmt by parse analysis. The QD will dispatch those other statements |
| * separately. |
| */ |
| if (Gp_role == GP_ROLE_EXECUTE) |
| stmts = list_make1(parsetree); |
| else |
| stmts = transformCreateStmt((CreateStmt *) parsetree, |
| queryString); |
| |
| /* |
| * GPDB_14_MERGE_FIXME |
| * GPDB will expand stmts list when there is part-partition definition and once used |
| * a label to iterate. |
| * Upstream expands stmts list too if there is 'LIKE' stmt. |
| * We follow the upstream method, the main difference is expanded partition list was appended |
| * to the end of stmt list after a range, but expanded runtime now.Need to check. |
| */ |
| |
| /* |
| * ... 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; |
| char relKind = RELKIND_RELATION; |
| Datum toast_options; |
| static char *validnsps[] = HEAP_RELOPT_NAMESPACES; |
| |
| /* |
| * If this T_CreateStmt was dispatched and we're a QE |
| * receiving it, extract the relkind and relstorage from |
| * it |
| */ |
| if (Gp_role == GP_ROLE_EXECUTE) |
| { |
| if (cstmt->relKind != 0) |
| relKind = cstmt->relKind; |
| } |
| else |
| cstmt->relKind = relKind; |
| |
| #if 0 |
| /* |
| * Upstream postgres does not support user specified |
| * RelOptions and TableAM for a parent partitioned |
| * table. The legacy Cloudberry behavior is to have |
| * the RelOptions and TableAM specified for the |
| * parent table to be inherited by the child |
| * partitions. Hence we need to remove these from |
| * the parent table's CreateStmt and store them to |
| * pass down to create the child partition's |
| * CreateStmt. This is only done when creating |
| * partitions with Cloudberry legacy syntax. |
| */ |
| if (cstmt->partspec && cstmt->partspec->gpPartDef) |
| { |
| options = cstmt->options; |
| cstmt->options = NIL; |
| cstmt->accessMethod = NULL; |
| } |
| #endif |
| |
| /* |
| * GPDB: Don't dispatch it yet, as we haven't |
| * created the toast and other auxiliary tables |
| * yet. |
| */ |
| /* Create the table itself */ |
| address = DefineRelation((CreateStmt *) stmt, |
| relKind, |
| ((CreateStmt *) stmt)->ownerid, NULL, |
| queryString, false, true, |
| cstmt->intoPolicy); |
| |
| if (cstmt->partspec && cstmt->partspec->gpPartDef) |
| { |
| List *parts; |
| |
| parts = generatePartitions(address.objectId, |
| cstmt->partspec->gpPartDef, |
| cstmt->partspec->subPartSpec, |
| queryString, cstmt->options, |
| cstmt->accessMethod, |
| cstmt->attr_encodings, |
| ORIGIN_GP_CLASSIC_CREATE_GEN); |
| stmts = list_concat(stmts, parts); |
| } |
| |
| /* Remember transformed RangeVar for LIKE */ |
| table_rv = cstmt->relation; |
| |
| EventTriggerCollectSimpleCommand(address, |
| secondaryObject, |
| stmt); |
| |
| /* |
| * Let NewRelationCreateToastTable decide if this |
| * one needs a secondary relation too. |
| */ |
| CommandCounterIncrement(); |
| |
| if (relKind != RELKIND_COMPOSITE_TYPE) |
| { |
| /* |
| * parse and validate reloptions for the toast |
| * table |
| */ |
| toast_options = transformRelOptions((Datum) 0, |
| cstmt->options, |
| "toast", |
| validnsps, |
| true, |
| false); |
| |
| NewRelationCreateToastTable(address.objectId, |
| toast_options); |
| } |
| if (Gp_role == GP_ROLE_DISPATCH && ENABLE_DISPATCH()) |
| { |
| CdbDispatchUtilityStatement((Node *) stmt, |
| DF_CANCEL_ON_ERROR | |
| DF_NEED_TWO_PHASE | |
| DF_WITH_SNAPSHOT, |
| GetAssignedOidsForDispatch(), |
| NULL); |
| } |
| else |
| { |
| /* |
| * Cloudberry specific behavior |
| * If intoQuery field is set, it means this is Create Matview. |
| * To keep catalog consistent, QEs should also store the viewquery. |
| * The call chain is: |
| * create_ctas_nodata()(QD) --> create_ctas_internal()(QD) --> |
| * dispatch create stmt(QD) --> ProcessUtilitySlow(on QE) --> StoreViewQuery()(QE). |
| */ |
| if (cstmt->intoQuery) |
| { |
| /* StoreViewQuery scribbles on tree, so make a copy */ |
| Query *query = (Query *) copyObject(cstmt->intoQuery); |
| |
| StoreViewQuery(address.objectId, query, false); |
| CommandCounterIncrement(); |
| } |
| } |
| } |
| 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, |
| true, |
| true, |
| NULL); |
| CreateForeignTable(cstmt, |
| address.objectId, |
| false /* skip_permission_checks */); |
| EventTriggerCollectSimpleCommand(address, |
| secondaryObject, |
| stmt); |
| } |
| else if (IsA(stmt, CreateDirectoryTableStmt)) |
| { |
| CreateDirectoryTableStmt *cstmt = (CreateDirectoryTableStmt *) stmt; |
| |
| /* Remember transformed RangeVar for LIKE */ |
| table_rv = cstmt->base.relation; |
| |
| /* Create the table itself */ |
| address = DefineRelation(&cstmt->base, |
| RELKIND_DIRECTORY_TABLE, |
| InvalidOid, NULL, |
| queryString, |
| true, |
| true, |
| cstmt->base.intoPolicy); |
| /* Create directory table tuple */ |
| CreateDirectoryTable(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_AddForeignSegStmt: |
| { |
| AddForeignSegStmt *afsstmt = (AddForeignSegStmt *) parsetree; |
| |
| AddForeignSeg(afsstmt); |
| } |
| 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. |
| */ |
| if (Gp_role == GP_ROLE_EXECUTE) |
| lockmode = atstmt->lockmode; |
| else |
| 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((Gp_role == GP_ROLE_EXECUTE) ? DEBUG1 : NOTICE, |
| |
| (errmsg("relation \"%s\" does not exist, skipping", |
| atstmt->relation->relname))); |
| } |
| |
| /* ALTER TABLE stashes commands internally */ |
| commandCollected = true; |
| break; |
| |
| case T_AlterDirectoryTableStmt: |
| { |
| AlterDirectoryTableStmt *stmt = (AlterDirectoryTableStmt *) parsetree; |
| Oid relid; |
| |
| /* |
| * Get relid of the directory table. |
| */ |
| relid = RangeVarGetRelidExtended(stmt->relation, |
| ShareUpdateExclusiveLock, |
| 0, |
| NULL, |
| NULL); |
| |
| if (stmt->tags) |
| { |
| if (!stmt->unsettag) |
| { |
| AlterTagDescriptions(stmt->tags, |
| MyDatabaseId, |
| RelationRelationId, |
| relid, |
| stmt->relation->relname); |
| } |
| |
| if (stmt->unsettag) |
| { |
| UnsetTagDescriptions(stmt->tags, |
| MyDatabaseId, |
| RelationRelationId, |
| relid, |
| stmt->relation->relname); |
| } |
| |
| } |
| |
| if (Gp_role == GP_ROLE_DISPATCH) |
| { |
| CdbDispatchUtilityStatement((Node *) stmt, |
| DF_CANCEL_ON_ERROR | |
| DF_WITH_SNAPSHOT | |
| DF_NEED_TWO_PHASE, |
| GetAssignedOidsForDispatch(), |
| NULL); |
| } |
| } |
| 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; |
| } |
| if (Gp_role == GP_ROLE_DISPATCH && ENABLE_DISPATCH()) |
| { |
| /* ADD CONSTRAINT will assign a new OID for the constraint */ |
| CdbDispatchUtilityStatement((Node *) stmt, |
| DF_CANCEL_ON_ERROR| |
| DF_WITH_SNAPSHOT| |
| DF_NEED_TWO_PHASE, |
| GetAssignedOidsForDispatch(), |
| NULL); |
| } |
| } |
| 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; |
| case OBJECT_EXTPROTOCOL: |
| Assert(stmt->args == NIL); |
| DefineExtProtocol(stmt->defnames, stmt->definition, stmt->trusted); |
| break; |
| default: |
| elog(ERROR, "unrecognized define stmt type: %d", |
| (int) stmt->kind); |
| break; |
| } |
| } |
| break; |
| |
| case T_CreateTaskStmt: |
| address = DefineTask(pstate, (CreateTaskStmt *) parsetree); |
| break; |
| |
| case T_AlterTaskStmt: |
| address = AlterTask(pstate, (AlterTaskStmt *) parsetree); |
| break; |
| |
| case T_DropTaskStmt: |
| address = DropTask(pstate, (DropTaskStmt *) parsetree); |
| break; |
| |
| case T_CreateWarehouseStmt: |
| case T_DropWarehouseStmt: |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("warehouse feature is not supported"))); |
| break; |
| |
| case T_CreateExternalStmt: |
| { |
| List *stmts; |
| ListCell *l; |
| |
| /* Run parse analysis ... */ |
| /* |
| * GPDB: Only do parse analysis in the Query Dispatcher. The Executor |
| * nodes receive an already-transformed statement from the QD. We only |
| * want to process the main CreateExternalStmt here, other such |
| * statements that would be created from the main |
| * CreateExternalStmt by parse analysis. The QD will dispatch |
| * those other statements separately. |
| */ |
| if (Gp_role == GP_ROLE_EXECUTE) |
| stmts = list_make1(parsetree); |
| else |
| stmts = transformCreateExternalStmt((CreateExternalStmt *) parsetree, queryString); |
| |
| /* ... and do it */ |
| foreach(l, stmts) |
| { |
| Node *stmt = (Node *) lfirst(l); |
| |
| if (IsA(stmt, CreateExternalStmt)) |
| DefineExternalRelation((CreateExternalStmt *) stmt); |
| else |
| { |
| 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; |
| |
| /* Recurse for anything else */ |
| ProcessUtility(wrapper, |
| queryString, |
| false, |
| PROCESS_UTILITY_SUBCOMMAND, |
| params, |
| NULL, |
| None_Receiver, |
| NULL); |
| } |
| } |
| } |
| break; |
| |
| case T_IndexStmt: /* CREATE INDEX */ |
| { |
| IndexStmt *stmt = (IndexStmt *) parsetree; |
| Oid relid; |
| LOCKMODE lockmode; |
| int nparts = -1; |
| 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; |
| |
| /* |
| * The QD might have looked up the OID of the base table |
| * already, and stashed it in stmt->relid |
| */ |
| if (stmt->relationOid) |
| relid = stmt->relationOid; |
| else |
| 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. While |
| * at it, also count the partitions, so that DefineIndex |
| * needn't do a duplicative find_all_inheritors search. |
| */ |
| 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) |
| { |
| Oid partrelid = lfirst_oid(lc); |
| char relkind = get_rel_relkind(partrelid); |
| |
| 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))); |
| } |
| /* count direct and indirect children, but not rel */ |
| nparts = list_length(inheritors) - 1; |
| 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 */ |
| nparts, /* # of partitions, or -1 */ |
| 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(pstate, (CreateFdwStmt *) parsetree); |
| break; |
| |
| case T_AlterFdwStmt: |
| address = AlterForeignDataWrapper(pstate, (AlterFdwStmt *) parsetree); |
| break; |
| |
| case T_CreateForeignServerStmt: |
| address = CreateForeignServer((CreateForeignServerStmt *) parsetree); |
| break; |
| |
| case T_AlterForeignServerStmt: |
| address = AlterForeignServer((AlterForeignServerStmt *) parsetree); |
| break; |
| |
| case T_CreateStorageServerStmt: |
| address = CreateStorageServer((CreateStorageServerStmt *) parsetree); |
| break; |
| |
| case T_AlterStorageServerStmt: |
| address = AlterStorageServer((AlterStorageServerStmt *) parsetree); |
| break; |
| |
| case T_DropStorageServerStmt: |
| RemoveStorageServer((DropStorageServerStmt *) parsetree); |
| commandCollected = true; |
| 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_CreateStorageUserMappingStmt: |
| address = CreateStorageUserMapping((CreateStorageUserMappingStmt *) parsetree); |
| break; |
| |
| case T_AlterStorageUserMappingStmt: |
| address = AlterStorageUserMapping((AlterStorageUserMappingStmt *) parsetree); |
| break; |
| |
| case T_DropStorageUserMappingStmt: |
| RemoveStorageUserMapping((DropStorageUserMappingStmt *) parsetree); |
| 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(pstate, (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(2); |
| { |
| address = ExecRefreshMatView((RefreshMatViewStmt *) parsetree, |
| queryString, params, qc); |
| } |
| PG_FINALLY(2); |
| { |
| EventTriggerUndoInhibitCommandCollection(); |
| } |
| PG_END_TRY(2); |
| break; |
| |
| case T_CreateTrigStmt: |
| address = CreateTrigger((CreateTrigStmt *) parsetree, |
| queryString, InvalidOid, InvalidOid, |
| InvalidOid, InvalidOid, InvalidOid, |
| InvalidOid, NULL, false, false); |
| if (Gp_role == GP_ROLE_DISPATCH && ENABLE_DISPATCH()) |
| { |
| CdbDispatchUtilityStatement((Node *) parsetree, |
| DF_CANCEL_ON_ERROR| |
| DF_WITH_SNAPSHOT| |
| DF_NEED_TWO_PHASE, |
| GetAssignedOidsForDispatch(), |
| NULL); |
| } |
| { |
| CreateTrigStmt *stmt = (CreateTrigStmt *) parsetree; |
| if (OidIsValid(stmt->matviewId)) |
| { |
| ObjectAddress refaddr; |
| refaddr.classId = RelationRelationId; |
| refaddr.objectId = stmt->matviewId; |
| refaddr.objectSubId = 0; |
| recordDependencyOn(&address, &refaddr, DEPENDENCY_AUTO); |
| } |
| } |
| 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_DropDirectoryTableStmt: |
| ExecDropStmt((DropStmt *) &((DropDirectoryTableStmt *) parsetree)->base, 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(pstate, (CreatePublicationStmt *) parsetree); |
| break; |
| |
| case T_AlterPublicationStmt: |
| AlterPublication(pstate, (AlterPublicationStmt *) parsetree); |
| |
| /* |
| * AlterPublication calls EventTriggerCollectSimpleCommand |
| * directly |
| */ |
| commandCollected = true; |
| break; |
| |
| case T_CreateSubscriptionStmt: |
| address = CreateSubscription(pstate, |
| (CreateSubscriptionStmt *) parsetree, |
| isTopLevel); |
| break; |
| |
| case T_AlterSubscriptionStmt: |
| address = AlterSubscription(pstate, |
| (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; |
| |
| /* |
| * We don't allow to dispatch some subcmds in ALTER TABLE before the QD has |
| * finished local execution. It causes the execution order different on QD |
| * and QEs, what's more some commands are not executed on QEs, this dispatched |
| * sub-commands will probably fail. |
| * In the end, the QD will dispatch the parse tree all in one after |
| * the QD has finished local execution, but before commit. |
| */ |
| HOLD_DISPATCH(); |
| ProcessUtility(wrapper, |
| context->queryString, |
| false, |
| PROCESS_UTILITY_SUBCOMMAND, |
| context->params, |
| context->queryEnv, |
| None_Receiver, |
| NULL); |
| RESUME_DISPATCH(); |
| |
| EventTriggerAlterTableStart(context->pstmt->utilityStmt); |
| EventTriggerAlterTableRelid(context->relid); |
| } |
| |
| /* |
| * Dispatch function for DropStmt |
| */ |
| static void |
| ExecDropStmt(DropStmt *stmt, bool isTopLevel) |
| { |
| DropStmt *copyStmt; |
| |
| /* stmt->objects could be modified (e.g. |
| * CREATE TABLE test_exists(a int, b int); |
| * DROP TRIGGER IF EXISTS test_trigger_exists ON test_exists;) |
| * so copy for later use. */ |
| copyStmt = copyObject(stmt); |
| |
| switch (stmt->removeType) |
| { |
| case OBJECT_INDEX: |
| if (stmt->concurrent && Gp_role != GP_ROLE_EXECUTE) |
| PreventInTransactionBlock(isTopLevel, |
| "DROP INDEX CONCURRENTLY"); |
| /* fall through */ |
| |
| case OBJECT_TABLE: |
| case OBJECT_SEQUENCE: |
| case OBJECT_VIEW: |
| case OBJECT_MATVIEW: |
| case OBJECT_FOREIGN_TABLE: |
| case OBJECT_DIRECTORY_TABLE: |
| RemoveRelations(stmt); |
| break; |
| |
| default: |
| RemoveObjects(stmt); |
| break; |
| } |
| |
| /* |
| * Dispatch the original, unmodified statement. |
| * |
| * Event triggers are not stored in QE nodes, so skip those. |
| */ |
| if (Gp_role == GP_ROLE_DISPATCH && shouldDispatchForObject(stmt->removeType) && ENABLE_DISPATCH()) |
| { |
| int flags; |
| |
| flags = DF_CANCEL_ON_ERROR | DF_NEED_TWO_PHASE; |
| |
| if (stmt->removeType == OBJECT_INDEX && stmt->concurrent) |
| { |
| /* |
| * Don't send a snapshot in DROP INDEX CONCURRENTLY. The QE is |
| * responsible for planning queries, so as soon as we have |
| * finished the dance on QD to drop the index, none of the QEs |
| * should need it either. So all the coordination we need across |
| * nodes is to complete the command in QD first, and QEs only |
| * after that. |
| */ |
| } |
| else |
| { |
| /* all other commands run normally, in a distributed transaction */ |
| flags |= DF_WITH_SNAPSHOT; |
| } |
| |
| CdbDispatchUtilityStatement((Node *) copyStmt, |
| flags, |
| NIL, |
| NULL); |
| } |
| } |
| |
| |
| /* |
| * 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; |
| |
| case T_RetrieveStmt: |
| 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); |
| } |
| |
| case T_RetrieveStmt: |
| { |
| RetrieveStmt *n = (RetrieveStmt *) parsetree; |
| |
| return CreateTupleDescCopy(GetRetrieveStmtTupleDesc(n)); |
| } |
| |
| 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_MERGE: |
| return false; |
| 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_PROFILE: |
| tag = CMDTAG_ALTER_PROFILE; |
| 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_TAG: |
| tag = CMDTAG_ALTER_TAG; |
| 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; |
| case OBJECT_EXTPROTOCOL: |
| tag = CMDTAG_ALTER_PROTOCOL; |
| 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_MergeStmt: |
| tag = CMDTAG_MERGE; |
| 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: |
| { |
| DeclareCursorStmt *stmt = (DeclareCursorStmt *) parsetree; |
| |
| if (stmt->options & CURSOR_OPT_PARALLEL_RETRIEVE) |
| tag = CMDTAG_DECLARE_PARALLEL_RETRIEVE_CURSOR; |
| else |
| 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_AlterSchemaStmt: |
| tag = CMDTAG_ALTER_SCHEMA; |
| break; |
| |
| case T_CreateStmt: |
| tag = CMDTAG_CREATE_TABLE; |
| break; |
| |
| case T_CreateExternalStmt: |
| tag = CMDTAG_CREATE_EXTERNAL; |
| 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_CreateTagStmt: |
| tag = CMDTAG_CREATE_TAG; |
| break; |
| |
| case T_AlterTagStmt: |
| tag = CMDTAG_ALTER_TAG; |
| break; |
| |
| case T_DropTagStmt: |
| tag = CMDTAG_DROP_TAG; |
| 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_CreateStorageServerStmt: |
| tag = CMDTAG_CREATE_STORAGE_SERVER; |
| break; |
| |
| case T_AlterStorageServerStmt: |
| tag = CMDTAG_ALTER_STORAGE_SERVER; |
| break; |
| |
| case T_DropStorageServerStmt: |
| tag = CMDTAG_DROP_STORAGE_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_CreateStorageUserMappingStmt: |
| tag = CMDTAG_CREATE_STORAGE_USER_MAPPING; |
| break; |
| |
| case T_AlterStorageUserMappingStmt: |
| tag = CMDTAG_ALTER_STORAGE_USER_MAPPING; |
| break; |
| |
| case T_DropStorageUserMappingStmt: |
| tag = CMDTAG_DROP_STORAGE_USER_MAPPING; |
| break; |
| |
| case T_CreateForeignTableStmt: |
| tag = CMDTAG_CREATE_FOREIGN_TABLE; |
| break; |
| |
| case T_AddForeignSegStmt: |
| tag = CMDTAG_ADD_FOREIGN_TABLE_SEG; |
| break; |
| |
| case T_ImportForeignSchemaStmt: |
| tag = CMDTAG_IMPORT_FOREIGN_SCHEMA; |
| break; |
| |
| case T_CreateDirectoryTableStmt: |
| tag = CMDTAG_CREATE_DIRECTORY_TABLE; |
| break; |
| |
| case T_AlterDirectoryTableStmt: |
| tag = CMDTAG_ALTER_DIRECTORY_TABLE; |
| break; |
| |
| case T_DropDirectoryTableStmt: |
| tag = CMDTAG_DROP_DIRECTORY_TABLE; |
| 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: |
| { |
| if (((DropStmt *) parsetree)->isdynamic) |
| tag = CMDTAG_DROP_DYNAMIC_TABLE; |
| else |
| 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_TABLESPACE: |
| tag = CMDTAG_DROP_TABLESPACE; |
| break; |
| case OBJECT_TAG: |
| tag = CMDTAG_DROP_TAG; |
| break; |
| case OBJECT_EXTPROTOCOL: |
| tag = CMDTAG_DROP_PROTOCOL; |
| 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_STORAGE_SERVER: |
| tag = CMDTAG_DROP_STORAGE_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; |
| case OBJECT_DIRECTORY_TABLE: |
| tag = CMDTAG_DROP_DIRECTORY_TABLE; |
| 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_EXTPROTOCOL: |
| tag = CMDTAG_CREATE_PROTOCOL; |
| 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_CreateTaskStmt: |
| tag = CMDTAG_CREATE_TASK; |
| break; |
| |
| case T_AlterTaskStmt: |
| tag = CMDTAG_ALTER_TASK; |
| break; |
| |
| case T_DropTaskStmt: |
| tag = CMDTAG_DROP_TASK; |
| 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: |
| case T_AlterDatabaseRefreshCollStmt: |
| 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: |
| { |
| if (((CreateTableAsStmt *) parsetree)->into->dynamicTbl) |
| tag = CMDTAG_CREATE_DYNAMIC_TABLE; |
| else |
| tag = CMDTAG_CREATE_MATERIALIZED_VIEW; |
| |
| break; |
| } |
| default: |
| tag = CMDTAG_UNKNOWN; |
| } |
| break; |
| |
| case T_RefreshMatViewStmt: |
| { |
| if (((RefreshMatViewStmt*) parsetree)->isdynamic) |
| tag = CMDTAG_REFRESH_DYNAMIC_TABLE; |
| else |
| 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_CreateQueueStmt: |
| tag = CMDTAG_CREATE_QUEUE; |
| break; |
| |
| case T_AlterQueueStmt: |
| tag = CMDTAG_ALTER_QUEUE; |
| break; |
| |
| case T_DropQueueStmt: |
| tag = CMDTAG_DROP_QUEUE; |
| break; |
| |
| case T_CreateResourceGroupStmt: |
| tag = CMDTAG_CREATE_RESOURCE_GROUP; |
| break; |
| |
| case T_DropResourceGroupStmt: |
| tag = CMDTAG_DROP_RESOURCE_GROUP; |
| break; |
| |
| case T_AlterResourceGroupStmt: |
| tag = CMDTAG_ALTER_RESOURCE_GROUP; |
| 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_CreateProfileStmt: |
| tag = CMDTAG_CREATE_PROFILE; |
| break; |
| |
| case T_AlterProfileStmt: |
| tag = CMDTAG_ALTER_PROFILE; |
| break; |
| |
| case T_DropProfileStmt: |
| tag = CMDTAG_DROP_PROFILE; |
| 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_MERGE: |
| tag = CMDTAG_MERGE; |
| 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_MERGE: |
| tag = CMDTAG_MERGE; |
| break; |
| case CMD_UTILITY: |
| tag = CreateCommandTag(stmt->utilityStmt); |
| break; |
| default: |
| elog(WARNING, "unrecognized commandType: %d", |
| (int) stmt->commandType); |
| tag = CMDTAG_UNKNOWN; |
| break; |
| } |
| } |
| break; |
| |
| case T_RetrieveStmt: |
| tag = CMDTAG_RETRIEVE; |
| break; |
| |
| case T_CreateWarehouseStmt: |
| tag = CMDTAG_CREATE_WAREHOUSE; |
| break; |
| case T_DropWarehouseStmt: |
| tag = CMDTAG_DROP_WAREHOUSE; |
| break; |
| |
| default: |
| elog(WARNING, "unrecognized node type: %d", |
| (int) nodeTag(parsetree)); |
| Assert(false); |
| 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: |
| case T_MergeStmt: |
| 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: |
| case T_AlterSchemaStmt: |
| lev = LOGSTMT_DDL; |
| break; |
| |
| case T_CreateStmt: |
| case T_CreateForeignTableStmt: |
| lev = LOGSTMT_DDL; |
| break; |
| |
| case T_AddForeignSegStmt: |
| lev = LOGSTMT_DDL; |
| break; |
| |
| case T_CreateExternalStmt: |
| 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_CreateStorageServerStmt: |
| case T_AlterStorageServerStmt: |
| case T_DropStorageServerStmt: |
| case T_CreateUserMappingStmt: |
| case T_AlterUserMappingStmt: |
| case T_DropUserMappingStmt: |
| case T_CreateStorageUserMappingStmt: |
| case T_AlterStorageUserMappingStmt: |
| case T_DropStorageUserMappingStmt: |
| case T_ImportForeignSchemaStmt: |
| case T_CreateDirectoryTableStmt: |
| case T_AlterDirectoryTableStmt: |
| case T_DropDirectoryTableStmt: |
| 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: |
| case T_AlterDatabaseRefreshCollStmt: |
| 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_CreateProfileStmt: |
| lev = LOGSTMT_DDL; |
| break; |
| |
| case T_AlterProfileStmt: |
| lev = LOGSTMT_DDL; |
| break; |
| |
| case T_DropProfileStmt: |
| 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; |
| |
| case T_CreateWarehouseStmt: |
| lev = LOGSTMT_DDL; |
| break; |
| |
| case T_DropWarehouseStmt: |
| 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: |
| case CMD_MERGE: |
| 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: |
| case CMD_MERGE: |
| 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; |
| |
| case T_CreateResourceGroupStmt: |
| case T_AlterResourceGroupStmt: |
| case T_DropResourceGroupStmt: |
| case T_CreateQueueStmt: |
| case T_AlterQueueStmt: |
| case T_DropQueueStmt: |
| lev = LOGSTMT_DDL; |
| break; |
| |
| default: |
| elog(WARNING, "unrecognized node type: %d", |
| (int) nodeTag(parsetree)); |
| lev = LOGSTMT_ALL; |
| break; |
| } |
| |
| return lev; |
| } |
| |
| /* Recover from an error for GPDB-specific code */ |
| void |
| GpRecoveryFromError() |
| { |
| /* |
| * Enable dispatch in QD when we hit an error. |
| * See HOLD_DISPATCH() |
| */ |
| CLEAR_DISPATCH(); |
| } |
| |
| bool |
| utility_nested(void) |
| { |
| return process_utility_nesting_level >= 2; |
| } |