3361 lines
78 KiB
C
3361 lines
78 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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-2017, 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/namespace.h"
|
|
#include "catalog/toasting.h"
|
|
#include "commands/alter.h"
|
|
#include "commands/async.h"
|
|
#include "commands/cluster.h"
|
|
#include "commands/comment.h"
|
|
#include "commands/collationcmds.h"
|
|
#include "commands/conversioncmds.h"
|
|
#include "commands/copy.h"
|
|
#include "commands/createas.h"
|
|
#include "commands/dbcommands.h"
|
|
#include "commands/defrem.h"
|
|
#include "commands/discard.h"
|
|
#include "commands/event_trigger.h"
|
|
#include "commands/explain.h"
|
|
#include "commands/extension.h"
|
|
#include "commands/matview.h"
|
|
#include "commands/lockcmds.h"
|
|
#include "commands/policy.h"
|
|
#include "commands/portalcmds.h"
|
|
#include "commands/prepare.h"
|
|
#include "commands/proclang.h"
|
|
#include "commands/publicationcmds.h"
|
|
#include "commands/schemacmds.h"
|
|
#include "commands/seclabel.h"
|
|
#include "commands/sequence.h"
|
|
#include "commands/subscriptioncmds.h"
|
|
#include "commands/tablecmds.h"
|
|
#include "commands/tablespace.h"
|
|
#include "commands/trigger.h"
|
|
#include "commands/typecmds.h"
|
|
#include "commands/user.h"
|
|
#include "commands/vacuum.h"
|
|
#include "commands/view.h"
|
|
#include "miscadmin.h"
|
|
#include "parser/parse_utilcmd.h"
|
|
#include "postmaster/bgwriter.h"
|
|
#include "rewrite/rewriteDefine.h"
|
|
#include "rewrite/rewriteRemove.h"
|
|
#include "storage/fd.h"
|
|
#include "tcop/pquery.h"
|
|
#include "tcop/utility.h"
|
|
#include "utils/acl.h"
|
|
#include "utils/guc.h"
|
|
#include "utils/syscache.h"
|
|
|
|
|
|
/* Hook for plugins to get control in ProcessUtility() */
|
|
ProcessUtility_hook_type ProcessUtility_hook = NULL;
|
|
|
|
/* local function declarations */
|
|
static void ProcessUtilitySlow(ParseState *pstate,
|
|
PlannedStmt *pstmt,
|
|
const char *queryString,
|
|
ProcessUtilityContext context,
|
|
ParamListInfo params,
|
|
QueryEnvironment *queryEnv,
|
|
DestReceiver *dest,
|
|
char *completionTag);
|
|
static void ExecDropStmt(DropStmt *stmt, bool isTopLevel);
|
|
|
|
|
|
/*
|
|
* CommandIsReadOnly: is an executable query read-only?
|
|
*
|
|
* This is a much stricter test than we apply for XactReadOnly mode;
|
|
* the query must be *in truth* read-only, because the caller wishes
|
|
* not to do CommandCounterIncrement for it.
|
|
*
|
|
* Note: currently no need to support raw or analyzed queries here
|
|
*/
|
|
bool
|
|
CommandIsReadOnly(PlannedStmt *pstmt)
|
|
{
|
|
Assert(IsA(pstmt, PlannedStmt));
|
|
switch (pstmt->commandType)
|
|
{
|
|
case CMD_SELECT:
|
|
if (pstmt->rowMarks != NIL)
|
|
return false; /* SELECT FOR [KEY] UPDATE/SHARE */
|
|
else if (pstmt->hasModifyingCTE)
|
|
return false; /* data-modifying CTE */
|
|
else
|
|
return true;
|
|
case CMD_UPDATE:
|
|
case CMD_INSERT:
|
|
case CMD_DELETE:
|
|
return false;
|
|
case CMD_UTILITY:
|
|
/* For now, treat all utility commands as read/write */
|
|
return false;
|
|
default:
|
|
elog(WARNING, "unrecognized commandType: %d",
|
|
(int) pstmt->commandType);
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* check_xact_readonly: is a utility command read-only?
|
|
*
|
|
* Here we use the loose rules of XactReadOnly mode: no permanent effects
|
|
* on the database are allowed.
|
|
*/
|
|
static void
|
|
check_xact_readonly(Node *parsetree)
|
|
{
|
|
/* Only perform the check if we have a reason to do so. */
|
|
if (!XactReadOnly && !IsInParallelMode())
|
|
return;
|
|
|
|
/*
|
|
* Note: Commands that need to do more complicated checking are handled
|
|
* elsewhere, in particular COPY and plannable statements do their own
|
|
* checking. However they should all call PreventCommandIfReadOnly or
|
|
* PreventCommandIfParallelMode to actually throw the error.
|
|
*/
|
|
|
|
switch (nodeTag(parsetree))
|
|
{
|
|
case T_AlterDatabaseStmt:
|
|
case T_AlterDatabaseSetStmt:
|
|
case T_AlterDomainStmt:
|
|
case T_AlterFunctionStmt:
|
|
case T_AlterRoleStmt:
|
|
case T_AlterRoleSetStmt:
|
|
case T_AlterObjectDependsStmt:
|
|
case T_AlterObjectSchemaStmt:
|
|
case T_AlterOwnerStmt:
|
|
case T_AlterOperatorStmt:
|
|
case T_AlterSeqStmt:
|
|
case T_AlterTableMoveAllStmt:
|
|
case T_AlterTableStmt:
|
|
case T_RenameStmt:
|
|
case T_CommentStmt:
|
|
case T_DefineStmt:
|
|
case T_CreateCastStmt:
|
|
case T_CreateEventTrigStmt:
|
|
case T_AlterEventTrigStmt:
|
|
case T_CreateConversionStmt:
|
|
case T_CreatedbStmt:
|
|
case T_CreateDomainStmt:
|
|
case T_CreateFunctionStmt:
|
|
case T_CreateRoleStmt:
|
|
case T_IndexStmt:
|
|
case T_CreatePLangStmt:
|
|
case T_CreateOpClassStmt:
|
|
case T_CreateOpFamilyStmt:
|
|
case T_AlterOpFamilyStmt:
|
|
case T_RuleStmt:
|
|
case T_CreateSchemaStmt:
|
|
case T_CreateSeqStmt:
|
|
case T_CreateStmt:
|
|
case T_CreateTableAsStmt:
|
|
case T_RefreshMatViewStmt:
|
|
case T_CreateTableSpaceStmt:
|
|
case T_CreateTransformStmt:
|
|
case T_CreateTrigStmt:
|
|
case T_CompositeTypeStmt:
|
|
case T_CreateEnumStmt:
|
|
case T_CreateRangeStmt:
|
|
case T_AlterEnumStmt:
|
|
case T_ViewStmt:
|
|
case T_DropStmt:
|
|
case T_DropdbStmt:
|
|
case T_DropTableSpaceStmt:
|
|
case T_DropRoleStmt:
|
|
case T_GrantStmt:
|
|
case T_GrantRoleStmt:
|
|
case T_AlterDefaultPrivilegesStmt:
|
|
case T_TruncateStmt:
|
|
case T_DropOwnedStmt:
|
|
case T_ReassignOwnedStmt:
|
|
case T_AlterTSDictionaryStmt:
|
|
case T_AlterTSConfigurationStmt:
|
|
case T_CreateExtensionStmt:
|
|
case T_AlterExtensionStmt:
|
|
case T_AlterExtensionContentsStmt:
|
|
case T_CreateFdwStmt:
|
|
case T_AlterFdwStmt:
|
|
case T_CreateForeignServerStmt:
|
|
case T_AlterForeignServerStmt:
|
|
case T_CreateUserMappingStmt:
|
|
case T_AlterUserMappingStmt:
|
|
case T_DropUserMappingStmt:
|
|
case T_AlterTableSpaceOptionsStmt:
|
|
case T_CreateForeignTableStmt:
|
|
case T_ImportForeignSchemaStmt:
|
|
case T_SecLabelStmt:
|
|
case T_CreatePublicationStmt:
|
|
case T_AlterPublicationStmt:
|
|
case T_CreateSubscriptionStmt:
|
|
case T_AlterSubscriptionStmt:
|
|
case T_DropSubscriptionStmt:
|
|
PreventCommandIfReadOnly(CreateCommandTag(parsetree));
|
|
PreventCommandIfParallelMode(CreateCommandTag(parsetree));
|
|
break;
|
|
default:
|
|
/* do nothing */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* PreventCommandIfReadOnly: throw error if XactReadOnly
|
|
*
|
|
* This is useful mainly to ensure consistency of the error message wording;
|
|
* most callers have checked XactReadOnly for themselves.
|
|
*/
|
|
void
|
|
PreventCommandIfReadOnly(const char *cmdname)
|
|
{
|
|
if (XactReadOnly)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
|
|
/* translator: %s is name of a SQL command, eg CREATE */
|
|
errmsg("cannot execute %s in a read-only transaction",
|
|
cmdname)));
|
|
}
|
|
|
|
/*
|
|
* PreventCommandIfParallelMode: throw error if current (sub)transaction is
|
|
* in parallel mode.
|
|
*
|
|
* This is useful mainly to ensure consistency of the error message wording;
|
|
* most callers have checked IsInParallelMode() for themselves.
|
|
*/
|
|
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 slave
|
|
* will be rejected by XactReadOnly tests. However there are a few
|
|
* commands that are allowed in "read-only" xacts but cannot be allowed
|
|
* in Hot Standby mode. Those commands should call this function.
|
|
*/
|
|
void
|
|
PreventCommandDuringRecovery(const char *cmdname)
|
|
{
|
|
if (RecoveryInProgress())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
|
|
/* translator: %s is name of a SQL command, eg CREATE */
|
|
errmsg("cannot execute %s during recovery",
|
|
cmdname)));
|
|
}
|
|
|
|
/*
|
|
* CheckRestrictedOperation: throw error for hazardous command if we're
|
|
* 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
|
|
* 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
|
|
* completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
|
|
* in which to store a command completion status string.
|
|
*
|
|
* 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)".
|
|
*
|
|
* completionTag is only set nonempty if we want to return a nondefault status.
|
|
*
|
|
* completionTag may be NULL if caller doesn't want a status string.
|
|
*
|
|
* 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,
|
|
ProcessUtilityContext context,
|
|
ParamListInfo params,
|
|
QueryEnvironment *queryEnv,
|
|
DestReceiver *dest,
|
|
char *completionTag)
|
|
{
|
|
Assert(IsA(pstmt, PlannedStmt));
|
|
Assert(pstmt->commandType == CMD_UTILITY);
|
|
Assert(queryString != NULL); /* required as of 8.4 */
|
|
|
|
/*
|
|
* 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,
|
|
context, params, queryEnv,
|
|
dest, completionTag);
|
|
else
|
|
standard_ProcessUtility(pstmt, queryString,
|
|
context, params, queryEnv,
|
|
dest, completionTag);
|
|
}
|
|
|
|
/*
|
|
* 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,
|
|
ProcessUtilityContext context,
|
|
ParamListInfo params,
|
|
QueryEnvironment *queryEnv,
|
|
DestReceiver *dest,
|
|
char *completionTag)
|
|
{
|
|
Node *parsetree = pstmt->utilityStmt;
|
|
bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
|
|
ParseState *pstate;
|
|
|
|
check_xact_readonly(parsetree);
|
|
|
|
if (completionTag)
|
|
completionTag[0] = '\0';
|
|
|
|
pstate = make_parsestate(NULL);
|
|
pstate->p_sourcetext = queryString;
|
|
|
|
switch (nodeTag(parsetree))
|
|
{
|
|
/*
|
|
* ******************** transactions ********************
|
|
*/
|
|
case T_TransactionStmt:
|
|
{
|
|
TransactionStmt *stmt = (TransactionStmt *) parsetree;
|
|
|
|
switch (stmt->kind)
|
|
{
|
|
/*
|
|
* START TRANSACTION, as defined by SQL99: Identical
|
|
* to BEGIN. Same code for both.
|
|
*/
|
|
case TRANS_STMT_BEGIN:
|
|
case TRANS_STMT_START:
|
|
{
|
|
ListCell *lc;
|
|
|
|
BeginTransactionBlock();
|
|
foreach(lc, stmt->options)
|
|
{
|
|
DefElem *item = (DefElem *) lfirst(lc);
|
|
|
|
if (strcmp(item->defname, "transaction_isolation") == 0)
|
|
SetPGVariable("transaction_isolation",
|
|
list_make1(item->arg),
|
|
true);
|
|
else if (strcmp(item->defname, "transaction_read_only") == 0)
|
|
SetPGVariable("transaction_read_only",
|
|
list_make1(item->arg),
|
|
true);
|
|
else if (strcmp(item->defname, "transaction_deferrable") == 0)
|
|
SetPGVariable("transaction_deferrable",
|
|
list_make1(item->arg),
|
|
true);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TRANS_STMT_COMMIT:
|
|
if (!EndTransactionBlock())
|
|
{
|
|
/* report unsuccessful commit in completionTag */
|
|
if (completionTag)
|
|
strcpy(completionTag, "ROLLBACK");
|
|
}
|
|
break;
|
|
|
|
case TRANS_STMT_PREPARE:
|
|
PreventCommandDuringRecovery("PREPARE TRANSACTION");
|
|
if (!PrepareTransactionBlock(stmt->gid))
|
|
{
|
|
/* report unsuccessful commit in completionTag */
|
|
if (completionTag)
|
|
strcpy(completionTag, "ROLLBACK");
|
|
}
|
|
break;
|
|
|
|
case TRANS_STMT_COMMIT_PREPARED:
|
|
PreventTransactionChain(isTopLevel, "COMMIT PREPARED");
|
|
PreventCommandDuringRecovery("COMMIT PREPARED");
|
|
FinishPreparedTransaction(stmt->gid, true);
|
|
break;
|
|
|
|
case TRANS_STMT_ROLLBACK_PREPARED:
|
|
PreventTransactionChain(isTopLevel, "ROLLBACK PREPARED");
|
|
PreventCommandDuringRecovery("ROLLBACK PREPARED");
|
|
FinishPreparedTransaction(stmt->gid, false);
|
|
break;
|
|
|
|
case TRANS_STMT_ROLLBACK:
|
|
UserAbortTransactionBlock();
|
|
break;
|
|
|
|
case TRANS_STMT_SAVEPOINT:
|
|
{
|
|
ListCell *cell;
|
|
char *name = NULL;
|
|
|
|
RequireTransactionChain(isTopLevel, "SAVEPOINT");
|
|
|
|
foreach(cell, stmt->options)
|
|
{
|
|
DefElem *elem = lfirst(cell);
|
|
|
|
if (strcmp(elem->defname, "savepoint_name") == 0)
|
|
name = strVal(elem->arg);
|
|
}
|
|
|
|
Assert(PointerIsValid(name));
|
|
|
|
DefineSavepoint(name);
|
|
}
|
|
break;
|
|
|
|
case TRANS_STMT_RELEASE:
|
|
RequireTransactionChain(isTopLevel, "RELEASE SAVEPOINT");
|
|
ReleaseSavepoint(stmt->options);
|
|
break;
|
|
|
|
case TRANS_STMT_ROLLBACK_TO:
|
|
RequireTransactionChain(isTopLevel, "ROLLBACK TO SAVEPOINT");
|
|
RollbackToSavepoint(stmt->options);
|
|
|
|
/*
|
|
* CommitTransactionCommand is in charge of
|
|
* re-defining the savepoint again
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* Portal (cursor) manipulation
|
|
*/
|
|
case T_DeclareCursorStmt:
|
|
PerformCursorOpen((DeclareCursorStmt *) parsetree, params,
|
|
queryString, isTopLevel);
|
|
break;
|
|
|
|
case T_ClosePortalStmt:
|
|
{
|
|
ClosePortalStmt *stmt = (ClosePortalStmt *) parsetree;
|
|
|
|
CheckRestrictedOperation("CLOSE");
|
|
PerformPortalClose(stmt->portalname);
|
|
}
|
|
break;
|
|
|
|
case T_FetchStmt:
|
|
PerformPortalFetch((FetchStmt *) parsetree, dest,
|
|
completionTag);
|
|
break;
|
|
|
|
case T_DoStmt:
|
|
ExecuteDoStmt((DoStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CreateTableSpaceStmt:
|
|
/* no event triggers for global objects */
|
|
PreventTransactionChain(isTopLevel, "CREATE TABLESPACE");
|
|
CreateTableSpace((CreateTableSpaceStmt *) parsetree);
|
|
break;
|
|
|
|
case T_DropTableSpaceStmt:
|
|
/* no event triggers for global objects */
|
|
PreventTransactionChain(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 (completionTag)
|
|
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
|
|
"COPY " UINT64_FORMAT, processed);
|
|
}
|
|
break;
|
|
|
|
case T_PrepareStmt:
|
|
CheckRestrictedOperation("PREPARE");
|
|
PrepareQuery((PrepareStmt *) parsetree, queryString,
|
|
pstmt->stmt_location, pstmt->stmt_len);
|
|
break;
|
|
|
|
case T_ExecuteStmt:
|
|
ExecuteQuery((ExecuteStmt *) parsetree, NULL,
|
|
queryString, params,
|
|
dest, completionTag);
|
|
break;
|
|
|
|
case T_DeallocateStmt:
|
|
CheckRestrictedOperation("DEALLOCATE");
|
|
DeallocateQuery((DeallocateStmt *) parsetree);
|
|
break;
|
|
|
|
case T_GrantRoleStmt:
|
|
/* no event triggers for global objects */
|
|
GrantRole((GrantRoleStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CreatedbStmt:
|
|
/* no event triggers for global objects */
|
|
PreventTransactionChain(isTopLevel, "CREATE DATABASE");
|
|
createdb(pstate, (CreatedbStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterDatabaseStmt:
|
|
/* no event triggers for global objects */
|
|
AlterDatabase(pstate, (AlterDatabaseStmt *) parsetree, isTopLevel);
|
|
break;
|
|
|
|
case T_AlterDatabaseSetStmt:
|
|
/* no event triggers for global objects */
|
|
AlterDatabaseSet((AlterDatabaseSetStmt *) parsetree);
|
|
break;
|
|
|
|
case T_DropdbStmt:
|
|
{
|
|
DropdbStmt *stmt = (DropdbStmt *) parsetree;
|
|
|
|
/* no event triggers for global objects */
|
|
PreventTransactionChain(isTopLevel, "DROP DATABASE");
|
|
dropdb(stmt->dbname, stmt->missing_ok);
|
|
}
|
|
break;
|
|
|
|
/* Query-level asynchronous notification */
|
|
case T_NotifyStmt:
|
|
{
|
|
NotifyStmt *stmt = (NotifyStmt *) parsetree;
|
|
|
|
PreventCommandDuringRecovery("NOTIFY");
|
|
Async_Notify(stmt->conditionname, stmt->payload);
|
|
}
|
|
break;
|
|
|
|
case T_ListenStmt:
|
|
{
|
|
ListenStmt *stmt = (ListenStmt *) parsetree;
|
|
|
|
PreventCommandDuringRecovery("LISTEN");
|
|
CheckRestrictedOperation("LISTEN");
|
|
Async_Listen(stmt->conditionname);
|
|
}
|
|
break;
|
|
|
|
case T_UnlistenStmt:
|
|
{
|
|
UnlistenStmt *stmt = (UnlistenStmt *) parsetree;
|
|
|
|
PreventCommandDuringRecovery("UNLISTEN");
|
|
CheckRestrictedOperation("UNLISTEN");
|
|
if (stmt->conditionname)
|
|
Async_Unlisten(stmt->conditionname);
|
|
else
|
|
Async_UnlistenAll();
|
|
}
|
|
break;
|
|
|
|
case T_LoadStmt:
|
|
{
|
|
LoadStmt *stmt = (LoadStmt *) parsetree;
|
|
|
|
closeAllVfds(); /* probably not necessary... */
|
|
/* Allowed names are restricted if you're not superuser */
|
|
load_file(stmt->filename, !superuser());
|
|
}
|
|
break;
|
|
|
|
case T_ClusterStmt:
|
|
/* we choose to allow this during "read only" transactions */
|
|
PreventCommandDuringRecovery("CLUSTER");
|
|
/* forbidden in parallel mode due to CommandIsReadOnly */
|
|
cluster((ClusterStmt *) parsetree, isTopLevel);
|
|
break;
|
|
|
|
case T_VacuumStmt:
|
|
{
|
|
VacuumStmt *stmt = (VacuumStmt *) parsetree;
|
|
|
|
/* we choose to allow this during "read only" transactions */
|
|
PreventCommandDuringRecovery((stmt->options & VACOPT_VACUUM) ?
|
|
"VACUUM" : "ANALYZE");
|
|
/* forbidden in parallel mode due to CommandIsReadOnly */
|
|
ExecVacuum(stmt, isTopLevel);
|
|
}
|
|
break;
|
|
|
|
case T_ExplainStmt:
|
|
ExplainQuery(pstate, (ExplainStmt *) parsetree, queryString, params,
|
|
queryEnv, dest);
|
|
break;
|
|
|
|
case T_AlterSystemStmt:
|
|
PreventTransactionChain(isTopLevel, "ALTER SYSTEM");
|
|
AlterSystemSetConfigFile((AlterSystemStmt *) parsetree);
|
|
break;
|
|
|
|
case T_VariableSetStmt:
|
|
ExecSetVariableStmt((VariableSetStmt *) parsetree, isTopLevel);
|
|
break;
|
|
|
|
case T_VariableShowStmt:
|
|
{
|
|
VariableShowStmt *n = (VariableShowStmt *) parsetree;
|
|
|
|
GetPGVariable(n->name, dest);
|
|
}
|
|
break;
|
|
|
|
case T_DiscardStmt:
|
|
/* should we allow DISCARD PLANS? */
|
|
CheckRestrictedOperation("DISCARD");
|
|
DiscardCommand((DiscardStmt *) parsetree, isTopLevel);
|
|
break;
|
|
|
|
case T_CreateEventTrigStmt:
|
|
/* no event triggers on event triggers */
|
|
CreateEventTrigger((CreateEventTrigStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterEventTrigStmt:
|
|
/* no event triggers on event triggers */
|
|
AlterEventTrigger((AlterEventTrigStmt *) parsetree);
|
|
break;
|
|
|
|
/*
|
|
* ******************************** ROLE statements ****
|
|
*/
|
|
case T_CreateRoleStmt:
|
|
/* no event triggers for global objects */
|
|
CreateRole(pstate, (CreateRoleStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterRoleStmt:
|
|
/* no event triggers for global objects */
|
|
AlterRole((AlterRoleStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterRoleSetStmt:
|
|
/* no event triggers for global objects */
|
|
AlterRoleSet((AlterRoleSetStmt *) parsetree);
|
|
break;
|
|
|
|
case T_DropRoleStmt:
|
|
/* no event triggers for global objects */
|
|
DropRole((DropRoleStmt *) parsetree);
|
|
break;
|
|
|
|
case T_ReassignOwnedStmt:
|
|
/* no event triggers for global objects */
|
|
ReassignOwnedObjects((ReassignOwnedStmt *) parsetree);
|
|
break;
|
|
|
|
case T_LockStmt:
|
|
|
|
/*
|
|
* Since the lock would just get dropped immediately, LOCK TABLE
|
|
* outside a transaction block is presumed to be user error.
|
|
*/
|
|
RequireTransactionChain(isTopLevel, "LOCK TABLE");
|
|
/* forbidden in parallel mode due to CommandIsReadOnly */
|
|
LockTableCommand((LockStmt *) parsetree);
|
|
break;
|
|
|
|
case T_ConstraintsSetStmt:
|
|
WarnNoTransactionChain(isTopLevel, "SET CONSTRAINTS");
|
|
AfterTriggerSetState((ConstraintsSetStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CheckPointStmt:
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to do CHECKPOINT")));
|
|
|
|
/*
|
|
* You might think we should have a PreventCommandDuringRecovery()
|
|
* here, but we interpret a CHECKPOINT command during recovery as
|
|
* a request for a restartpoint instead. We allow this since it
|
|
* can be a useful way of reducing switchover time when using
|
|
* various forms of replication.
|
|
*/
|
|
RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT |
|
|
(RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
|
|
break;
|
|
|
|
case T_ReindexStmt:
|
|
{
|
|
ReindexStmt *stmt = (ReindexStmt *) parsetree;
|
|
|
|
/* we choose to allow this during "read only" transactions */
|
|
PreventCommandDuringRecovery("REINDEX");
|
|
/* forbidden in parallel mode due to CommandIsReadOnly */
|
|
switch (stmt->kind)
|
|
{
|
|
case REINDEX_OBJECT_INDEX:
|
|
ReindexIndex(stmt->relation, stmt->options);
|
|
break;
|
|
case REINDEX_OBJECT_TABLE:
|
|
ReindexTable(stmt->relation, stmt->options);
|
|
break;
|
|
case REINDEX_OBJECT_SCHEMA:
|
|
case REINDEX_OBJECT_SYSTEM:
|
|
case REINDEX_OBJECT_DATABASE:
|
|
|
|
/*
|
|
* This cannot run inside a user transaction block; if
|
|
* we were inside a transaction, then its commit- and
|
|
* start-transaction-command calls would not have the
|
|
* intended effect!
|
|
*/
|
|
PreventTransactionChain(isTopLevel,
|
|
(stmt->kind == REINDEX_OBJECT_SCHEMA) ? "REINDEX SCHEMA" :
|
|
(stmt->kind == REINDEX_OBJECT_SYSTEM) ? "REINDEX SYSTEM" :
|
|
"REINDEX DATABASE");
|
|
ReindexMultipleTables(stmt->name, stmt->kind, stmt->options);
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized object type: %d",
|
|
(int) stmt->kind);
|
|
break;
|
|
}
|
|
}
|
|
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 (EventTriggerSupportsGrantObjectType(stmt->objtype))
|
|
ProcessUtilitySlow(pstate, pstmt, queryString,
|
|
context, params, queryEnv,
|
|
dest, completionTag);
|
|
else
|
|
ExecuteGrantStmt(stmt);
|
|
}
|
|
break;
|
|
|
|
case T_DropStmt:
|
|
{
|
|
DropStmt *stmt = (DropStmt *) parsetree;
|
|
|
|
if (EventTriggerSupportsObjectType(stmt->removeType))
|
|
ProcessUtilitySlow(pstate, pstmt, queryString,
|
|
context, params, queryEnv,
|
|
dest, completionTag);
|
|
else
|
|
ExecDropStmt(stmt, isTopLevel);
|
|
}
|
|
break;
|
|
|
|
case T_RenameStmt:
|
|
{
|
|
RenameStmt *stmt = (RenameStmt *) parsetree;
|
|
|
|
if (EventTriggerSupportsObjectType(stmt->renameType))
|
|
ProcessUtilitySlow(pstate, pstmt, queryString,
|
|
context, params, queryEnv,
|
|
dest, completionTag);
|
|
else
|
|
ExecRenameStmt(stmt);
|
|
}
|
|
break;
|
|
|
|
case T_AlterObjectDependsStmt:
|
|
{
|
|
AlterObjectDependsStmt *stmt = (AlterObjectDependsStmt *) parsetree;
|
|
|
|
if (EventTriggerSupportsObjectType(stmt->objectType))
|
|
ProcessUtilitySlow(pstate, pstmt, queryString,
|
|
context, params, queryEnv,
|
|
dest, completionTag);
|
|
else
|
|
ExecAlterObjectDependsStmt(stmt, NULL);
|
|
}
|
|
break;
|
|
|
|
case T_AlterObjectSchemaStmt:
|
|
{
|
|
AlterObjectSchemaStmt *stmt = (AlterObjectSchemaStmt *) parsetree;
|
|
|
|
if (EventTriggerSupportsObjectType(stmt->objectType))
|
|
ProcessUtilitySlow(pstate, pstmt, queryString,
|
|
context, params, queryEnv,
|
|
dest, completionTag);
|
|
else
|
|
ExecAlterObjectSchemaStmt(stmt, NULL);
|
|
}
|
|
break;
|
|
|
|
case T_AlterOwnerStmt:
|
|
{
|
|
AlterOwnerStmt *stmt = (AlterOwnerStmt *) parsetree;
|
|
|
|
if (EventTriggerSupportsObjectType(stmt->objectType))
|
|
ProcessUtilitySlow(pstate, pstmt, queryString,
|
|
context, params, queryEnv,
|
|
dest, completionTag);
|
|
else
|
|
ExecAlterOwnerStmt(stmt);
|
|
}
|
|
break;
|
|
|
|
case T_CommentStmt:
|
|
{
|
|
CommentStmt *stmt = (CommentStmt *) parsetree;
|
|
|
|
if (EventTriggerSupportsObjectType(stmt->objtype))
|
|
ProcessUtilitySlow(pstate, pstmt, queryString,
|
|
context, params, queryEnv,
|
|
dest, completionTag);
|
|
else
|
|
CommentObject(stmt);
|
|
break;
|
|
}
|
|
|
|
case T_SecLabelStmt:
|
|
{
|
|
SecLabelStmt *stmt = (SecLabelStmt *) parsetree;
|
|
|
|
if (EventTriggerSupportsObjectType(stmt->objtype))
|
|
ProcessUtilitySlow(pstate, pstmt, queryString,
|
|
context, params, queryEnv,
|
|
dest, completionTag);
|
|
else
|
|
ExecSecLabelStmt(stmt);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
/* All other statement types have event trigger support */
|
|
ProcessUtilitySlow(pstate, pstmt, queryString,
|
|
context, params, queryEnv,
|
|
dest, completionTag);
|
|
break;
|
|
}
|
|
|
|
free_parsestate(pstate);
|
|
}
|
|
|
|
/*
|
|
* 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,
|
|
char *completionTag)
|
|
{
|
|
Node *parsetree = pstmt->utilityStmt;
|
|
bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
|
|
bool isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
|
|
bool needCleanup;
|
|
bool commandCollected = false;
|
|
ObjectAddress address;
|
|
ObjectAddress secondaryObject = InvalidObjectAddress;
|
|
|
|
/* All event trigger calls are done only when isCompleteQuery is true */
|
|
needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
|
|
|
|
/* PG_TRY block is to ensure we call EventTriggerEndCompleteQuery */
|
|
PG_TRY();
|
|
{
|
|
if (isCompleteQuery)
|
|
EventTriggerDDLCommandStart(parsetree);
|
|
|
|
switch (nodeTag(parsetree))
|
|
{
|
|
/*
|
|
* relation and attribute manipulation
|
|
*/
|
|
case T_CreateSchemaStmt:
|
|
CreateSchemaCommand((CreateSchemaStmt *) parsetree,
|
|
queryString,
|
|
pstmt->stmt_location,
|
|
pstmt->stmt_len);
|
|
|
|
/*
|
|
* EventTriggerCollectSimpleCommand called by
|
|
* CreateSchemaCommand
|
|
*/
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_CreateStmt:
|
|
case T_CreateForeignTableStmt:
|
|
{
|
|
List *stmts;
|
|
ListCell *l;
|
|
|
|
/* Run parse analysis ... */
|
|
stmts = transformCreateStmt((CreateStmt *) parsetree,
|
|
queryString);
|
|
|
|
/* ... and do it */
|
|
foreach(l, stmts)
|
|
{
|
|
Node *stmt = (Node *) lfirst(l);
|
|
|
|
if (IsA(stmt, CreateStmt))
|
|
{
|
|
Datum toast_options;
|
|
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
|
|
|
|
/* Create the table itself */
|
|
address = DefineRelation((CreateStmt *) stmt,
|
|
RELKIND_RELATION,
|
|
InvalidOid, NULL,
|
|
queryString);
|
|
EventTriggerCollectSimpleCommand(address,
|
|
secondaryObject,
|
|
stmt);
|
|
|
|
/*
|
|
* Let NewRelationCreateToastTable decide if this
|
|
* one needs a secondary relation too.
|
|
*/
|
|
CommandCounterIncrement();
|
|
|
|
/*
|
|
* parse and validate reloptions for the toast
|
|
* table
|
|
*/
|
|
toast_options = transformRelOptions((Datum) 0,
|
|
((CreateStmt *) stmt)->options,
|
|
"toast",
|
|
validnsps,
|
|
true,
|
|
false);
|
|
(void) heap_reloptions(RELKIND_TOASTVALUE,
|
|
toast_options,
|
|
true);
|
|
|
|
NewRelationCreateToastTable(address.objectId,
|
|
toast_options);
|
|
}
|
|
else if (IsA(stmt, CreateForeignTableStmt))
|
|
{
|
|
/* Create the table itself */
|
|
address = DefineRelation((CreateStmt *) stmt,
|
|
RELKIND_FOREIGN_TABLE,
|
|
InvalidOid, NULL,
|
|
queryString);
|
|
CreateForeignTable((CreateForeignTableStmt *) stmt,
|
|
address.objectId);
|
|
EventTriggerCollectSimpleCommand(address,
|
|
secondaryObject,
|
|
stmt);
|
|
}
|
|
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,
|
|
PROCESS_UTILITY_SUBCOMMAND,
|
|
params,
|
|
NULL,
|
|
None_Receiver,
|
|
NULL);
|
|
}
|
|
|
|
/* Need CCI between commands */
|
|
if (lnext(l) != NULL)
|
|
CommandCounterIncrement();
|
|
}
|
|
|
|
/*
|
|
* The multiple commands generated here are stashed
|
|
* individually, so disable collection below.
|
|
*/
|
|
commandCollected = true;
|
|
}
|
|
break;
|
|
|
|
case T_AlterTableStmt:
|
|
{
|
|
AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
|
|
Oid relid;
|
|
List *stmts;
|
|
ListCell *l;
|
|
LOCKMODE lockmode;
|
|
|
|
/*
|
|
* Figure out lock mode, and acquire lock. This also does
|
|
* basic permissions checks, so that we won't wait for a
|
|
* lock on (for example) a relation on which we have no
|
|
* permissions.
|
|
*/
|
|
lockmode = AlterTableGetLockLevel(atstmt->cmds);
|
|
relid = AlterTableLookupRelation(atstmt, lockmode);
|
|
|
|
if (OidIsValid(relid))
|
|
{
|
|
/* Run parse analysis ... */
|
|
stmts = transformAlterTableStmt(relid, atstmt,
|
|
queryString);
|
|
|
|
/* ... ensure we have an event trigger context ... */
|
|
EventTriggerAlterTableStart(parsetree);
|
|
EventTriggerAlterTableRelid(relid);
|
|
|
|
/* ... and do it */
|
|
foreach(l, stmts)
|
|
{
|
|
Node *stmt = (Node *) lfirst(l);
|
|
|
|
if (IsA(stmt, AlterTableStmt))
|
|
{
|
|
/* Do the table alteration proper */
|
|
AlterTable(relid, lockmode,
|
|
(AlterTableStmt *) stmt);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Recurse for anything else. If we need to
|
|
* do so, "close" the current complex-command
|
|
* set, and start a new one at the bottom;
|
|
* this is needed to ensure the ordering of
|
|
* queued commands is consistent with the way
|
|
* they are executed here.
|
|
*/
|
|
PlannedStmt *wrapper;
|
|
|
|
EventTriggerAlterTableEnd();
|
|
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,
|
|
PROCESS_UTILITY_SUBCOMMAND,
|
|
params,
|
|
NULL,
|
|
None_Receiver,
|
|
NULL);
|
|
EventTriggerAlterTableStart(parsetree);
|
|
EventTriggerAlterTableRelid(relid);
|
|
}
|
|
|
|
/* Need CCI between commands */
|
|
if (lnext(l) != NULL)
|
|
CommandCounterIncrement();
|
|
}
|
|
|
|
/* done */
|
|
EventTriggerAlterTableEnd();
|
|
}
|
|
else
|
|
ereport(NOTICE,
|
|
(errmsg("relation \"%s\" does not exist, skipping",
|
|
atstmt->relation->relname)));
|
|
}
|
|
|
|
/* ALTER TABLE stashes commands internally */
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_AlterDomainStmt:
|
|
{
|
|
AlterDomainStmt *stmt = (AlterDomainStmt *) parsetree;
|
|
|
|
/*
|
|
* Some or all of these functions are recursive to cover
|
|
* inherited things, so permission checks are done there.
|
|
*/
|
|
switch (stmt->subtype)
|
|
{
|
|
case 'T': /* ALTER DOMAIN DEFAULT */
|
|
|
|
/*
|
|
* Recursively alter column default for table and,
|
|
* if requested, for descendants
|
|
*/
|
|
address =
|
|
AlterDomainDefault(stmt->typeName,
|
|
stmt->def);
|
|
break;
|
|
case 'N': /* ALTER DOMAIN DROP NOT NULL */
|
|
address =
|
|
AlterDomainNotNull(stmt->typeName,
|
|
false);
|
|
break;
|
|
case 'O': /* ALTER DOMAIN SET NOT NULL */
|
|
address =
|
|
AlterDomainNotNull(stmt->typeName,
|
|
true);
|
|
break;
|
|
case 'C': /* ADD CONSTRAINT */
|
|
address =
|
|
AlterDomainAddConstraint(stmt->typeName,
|
|
stmt->def,
|
|
&secondaryObject);
|
|
break;
|
|
case 'X': /* DROP CONSTRAINT */
|
|
address =
|
|
AlterDomainDropConstraint(stmt->typeName,
|
|
stmt->name,
|
|
stmt->behavior,
|
|
stmt->missing_ok);
|
|
break;
|
|
case 'V': /* VALIDATE CONSTRAINT */
|
|
address =
|
|
AlterDomainValidateConstraint(stmt->typeName,
|
|
stmt->name);
|
|
break;
|
|
default: /* oops */
|
|
elog(ERROR, "unrecognized alter domain type: %d",
|
|
(int) stmt->subtype);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* ************* object creation / destruction **************
|
|
*/
|
|
case T_DefineStmt:
|
|
{
|
|
DefineStmt *stmt = (DefineStmt *) parsetree;
|
|
|
|
switch (stmt->kind)
|
|
{
|
|
case OBJECT_AGGREGATE:
|
|
address =
|
|
DefineAggregate(pstate, stmt->defnames, stmt->args,
|
|
stmt->oldstyle,
|
|
stmt->definition);
|
|
break;
|
|
case OBJECT_OPERATOR:
|
|
Assert(stmt->args == NIL);
|
|
address = DefineOperator(stmt->defnames,
|
|
stmt->definition);
|
|
break;
|
|
case OBJECT_TYPE:
|
|
Assert(stmt->args == NIL);
|
|
address = DefineType(pstate,
|
|
stmt->defnames,
|
|
stmt->definition);
|
|
break;
|
|
case OBJECT_TSPARSER:
|
|
Assert(stmt->args == NIL);
|
|
address = DefineTSParser(stmt->defnames,
|
|
stmt->definition);
|
|
break;
|
|
case OBJECT_TSDICTIONARY:
|
|
Assert(stmt->args == NIL);
|
|
address = DefineTSDictionary(stmt->defnames,
|
|
stmt->definition);
|
|
break;
|
|
case OBJECT_TSTEMPLATE:
|
|
Assert(stmt->args == NIL);
|
|
address = DefineTSTemplate(stmt->defnames,
|
|
stmt->definition);
|
|
break;
|
|
case OBJECT_TSCONFIGURATION:
|
|
Assert(stmt->args == NIL);
|
|
address = DefineTSConfiguration(stmt->defnames,
|
|
stmt->definition,
|
|
&secondaryObject);
|
|
break;
|
|
case OBJECT_COLLATION:
|
|
Assert(stmt->args == NIL);
|
|
address = DefineCollation(pstate,
|
|
stmt->defnames,
|
|
stmt->definition,
|
|
stmt->if_not_exists);
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized define stmt type: %d",
|
|
(int) stmt->kind);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case T_IndexStmt: /* CREATE INDEX */
|
|
{
|
|
IndexStmt *stmt = (IndexStmt *) parsetree;
|
|
Oid relid;
|
|
LOCKMODE lockmode;
|
|
|
|
if (stmt->concurrent)
|
|
PreventTransactionChain(isTopLevel,
|
|
"CREATE INDEX CONCURRENTLY");
|
|
|
|
/*
|
|
* Look up the relation OID just once, right here at the
|
|
* beginning, so that we don't end up repeating the name
|
|
* lookup later and latching onto a different relation
|
|
* partway through. To avoid lock upgrade hazards, it's
|
|
* important that we take the strongest lock that will
|
|
* eventually be needed here, so the lockmode calculation
|
|
* needs to match what DefineIndex() does.
|
|
*/
|
|
lockmode = stmt->concurrent ? ShareUpdateExclusiveLock
|
|
: ShareLock;
|
|
relid =
|
|
RangeVarGetRelidExtended(stmt->relation, lockmode,
|
|
false, false,
|
|
RangeVarCallbackOwnsRelation,
|
|
NULL);
|
|
|
|
/* 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 */
|
|
false, /* is_alter_table */
|
|
true, /* check_rights */
|
|
false, /* skip_build */
|
|
false); /* quiet */
|
|
|
|
/*
|
|
* Add the CREATE INDEX node itself to stash right away;
|
|
* if there were any commands stashed in the ALTER TABLE
|
|
* code, we need them to appear after this one.
|
|
*/
|
|
EventTriggerCollectSimpleCommand(address, secondaryObject,
|
|
parsetree);
|
|
commandCollected = true;
|
|
EventTriggerAlterTableEnd();
|
|
}
|
|
break;
|
|
|
|
case T_CreateExtensionStmt:
|
|
address = CreateExtension(pstate, (CreateExtensionStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterExtensionStmt:
|
|
address = ExecAlterExtensionStmt(pstate, (AlterExtensionStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterExtensionContentsStmt:
|
|
address = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
|
|
&secondaryObject);
|
|
break;
|
|
|
|
case T_CreateFdwStmt:
|
|
address = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterFdwStmt:
|
|
address = AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CreateForeignServerStmt:
|
|
address = CreateForeignServer((CreateForeignServerStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterForeignServerStmt:
|
|
address = AlterForeignServer((AlterForeignServerStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CreateUserMappingStmt:
|
|
address = CreateUserMapping((CreateUserMappingStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterUserMappingStmt:
|
|
address = AlterUserMapping((AlterUserMappingStmt *) parsetree);
|
|
break;
|
|
|
|
case T_DropUserMappingStmt:
|
|
RemoveUserMapping((DropUserMappingStmt *) parsetree);
|
|
/* no commands stashed for DROP */
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_ImportForeignSchemaStmt:
|
|
ImportForeignSchema((ImportForeignSchemaStmt *) parsetree);
|
|
/* commands are stashed inside ImportForeignSchema */
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_CompositeTypeStmt: /* CREATE TYPE (composite) */
|
|
{
|
|
CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
|
|
|
|
address = DefineCompositeType(stmt->typevar,
|
|
stmt->coldeflist);
|
|
}
|
|
break;
|
|
|
|
case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */
|
|
address = DefineEnum((CreateEnumStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */
|
|
address = DefineRange((CreateRangeStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterEnumStmt: /* ALTER TYPE (enum) */
|
|
address = AlterEnum((AlterEnumStmt *) parsetree);
|
|
break;
|
|
|
|
case T_ViewStmt: /* CREATE VIEW */
|
|
EventTriggerAlterTableStart(parsetree);
|
|
address = DefineView((ViewStmt *) parsetree, queryString,
|
|
pstmt->stmt_location, pstmt->stmt_len);
|
|
EventTriggerCollectSimpleCommand(address, secondaryObject,
|
|
parsetree);
|
|
/* stashed internally */
|
|
commandCollected = true;
|
|
EventTriggerAlterTableEnd();
|
|
break;
|
|
|
|
case T_CreateFunctionStmt: /* CREATE FUNCTION */
|
|
address = CreateFunction(pstate, (CreateFunctionStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterFunctionStmt: /* ALTER FUNCTION */
|
|
address = AlterFunction(pstate, (AlterFunctionStmt *) parsetree);
|
|
break;
|
|
|
|
case T_RuleStmt: /* CREATE RULE */
|
|
address = DefineRule((RuleStmt *) parsetree, queryString);
|
|
break;
|
|
|
|
case T_CreateSeqStmt:
|
|
address = DefineSequence(pstate, (CreateSeqStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterSeqStmt:
|
|
address = AlterSequence(pstate, (AlterSeqStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CreateTableAsStmt:
|
|
address = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
|
|
queryString, params, queryEnv,
|
|
completionTag);
|
|
break;
|
|
|
|
case T_RefreshMatViewStmt:
|
|
/*
|
|
* REFRESH CONCURRENTLY executes some DDL commands internally.
|
|
* Inhibit DDL command collection here to avoid those commands
|
|
* from showing up in the deparsed command queue. The refresh
|
|
* command itself is queued, which is enough.
|
|
*/
|
|
EventTriggerInhibitCommandCollection();
|
|
PG_TRY();
|
|
{
|
|
address = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
|
|
queryString, params, completionTag);
|
|
}
|
|
PG_CATCH();
|
|
{
|
|
EventTriggerUndoInhibitCommandCollection();
|
|
PG_RE_THROW();
|
|
}
|
|
PG_END_TRY();
|
|
EventTriggerUndoInhibitCommandCollection();
|
|
break;
|
|
|
|
case T_CreateTrigStmt:
|
|
address = CreateTrigger((CreateTrigStmt *) parsetree,
|
|
queryString, InvalidOid, InvalidOid,
|
|
InvalidOid, InvalidOid, false);
|
|
break;
|
|
|
|
case T_CreatePLangStmt:
|
|
address = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CreateDomainStmt:
|
|
address = DefineDomain((CreateDomainStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CreateConversionStmt:
|
|
address = CreateConversionCommand((CreateConversionStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CreateCastStmt:
|
|
address = CreateCast((CreateCastStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CreateOpClassStmt:
|
|
DefineOpClass((CreateOpClassStmt *) parsetree);
|
|
/* command is stashed in DefineOpClass */
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_CreateOpFamilyStmt:
|
|
address = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CreateTransformStmt:
|
|
address = CreateTransform((CreateTransformStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterOpFamilyStmt:
|
|
AlterOpFamily((AlterOpFamilyStmt *) parsetree);
|
|
/* commands are stashed in AlterOpFamily */
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_AlterTSDictionaryStmt:
|
|
address = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterTSConfigurationStmt:
|
|
AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
|
|
|
|
/*
|
|
* Commands are stashed in MakeConfigurationMapping and
|
|
* DropConfigurationMapping, which are called from
|
|
* AlterTSConfiguration
|
|
*/
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_AlterTableMoveAllStmt:
|
|
AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
|
|
/* commands are stashed in AlterTableMoveAll */
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_DropStmt:
|
|
ExecDropStmt((DropStmt *) parsetree, isTopLevel);
|
|
/* no commands stashed for DROP */
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_RenameStmt:
|
|
address = ExecRenameStmt((RenameStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterObjectDependsStmt:
|
|
address =
|
|
ExecAlterObjectDependsStmt((AlterObjectDependsStmt *) parsetree,
|
|
&secondaryObject);
|
|
break;
|
|
|
|
case T_AlterObjectSchemaStmt:
|
|
address =
|
|
ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
|
|
&secondaryObject);
|
|
break;
|
|
|
|
case T_AlterOwnerStmt:
|
|
address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterOperatorStmt:
|
|
address = AlterOperator((AlterOperatorStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CommentStmt:
|
|
address = CommentObject((CommentStmt *) parsetree);
|
|
break;
|
|
|
|
case T_GrantStmt:
|
|
ExecuteGrantStmt((GrantStmt *) parsetree);
|
|
/* commands are stashed in ExecGrantStmt_oids */
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_DropOwnedStmt:
|
|
DropOwnedObjects((DropOwnedStmt *) parsetree);
|
|
/* no commands stashed for DROP */
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_AlterDefaultPrivilegesStmt:
|
|
ExecAlterDefaultPrivilegesStmt(pstate, (AlterDefaultPrivilegesStmt *) parsetree);
|
|
EventTriggerCollectAlterDefPrivs((AlterDefaultPrivilegesStmt *) parsetree);
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_CreatePolicyStmt: /* CREATE POLICY */
|
|
address = CreatePolicy((CreatePolicyStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterPolicyStmt: /* ALTER POLICY */
|
|
address = AlterPolicy((AlterPolicyStmt *) parsetree);
|
|
break;
|
|
|
|
case T_SecLabelStmt:
|
|
address = ExecSecLabelStmt((SecLabelStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CreateAmStmt:
|
|
address = CreateAccessMethod((CreateAmStmt *) parsetree);
|
|
break;
|
|
|
|
case T_CreatePublicationStmt:
|
|
address = CreatePublication((CreatePublicationStmt *) parsetree);
|
|
break;
|
|
|
|
case T_AlterPublicationStmt:
|
|
AlterPublication((AlterPublicationStmt *) parsetree);
|
|
/*
|
|
* AlterPublication calls EventTriggerCollectSimpleCommand
|
|
* directly
|
|
*/
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_CreateSubscriptionStmt:
|
|
address = CreateSubscription((CreateSubscriptionStmt *) parsetree,
|
|
isTopLevel);
|
|
break;
|
|
|
|
case T_AlterSubscriptionStmt:
|
|
address = AlterSubscription((AlterSubscriptionStmt *) parsetree);
|
|
break;
|
|
|
|
case T_DropSubscriptionStmt:
|
|
DropSubscription((DropSubscriptionStmt *) parsetree, isTopLevel);
|
|
/* no commands stashed for DROP */
|
|
commandCollected = true;
|
|
break;
|
|
|
|
case T_CreateStatsStmt:
|
|
address = CreateStatistics((CreateStatsStmt *) 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_CATCH();
|
|
{
|
|
if (needCleanup)
|
|
EventTriggerEndCompleteQuery();
|
|
PG_RE_THROW();
|
|
}
|
|
PG_END_TRY();
|
|
|
|
if (needCleanup)
|
|
EventTriggerEndCompleteQuery();
|
|
}
|
|
|
|
/*
|
|
* Dispatch function for DropStmt
|
|
*/
|
|
static void
|
|
ExecDropStmt(DropStmt *stmt, bool isTopLevel)
|
|
{
|
|
switch (stmt->removeType)
|
|
{
|
|
case OBJECT_INDEX:
|
|
if (stmt->concurrent)
|
|
PreventTransactionChain(isTopLevel,
|
|
"DROP INDEX CONCURRENTLY");
|
|
/* fall through */
|
|
|
|
case OBJECT_TABLE:
|
|
case OBJECT_SEQUENCE:
|
|
case OBJECT_VIEW:
|
|
case OBJECT_MATVIEW:
|
|
case OBJECT_FOREIGN_TABLE:
|
|
RemoveRelations(stmt);
|
|
break;
|
|
default:
|
|
RemoveObjects(stmt);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* UtilityReturnsTuples
|
|
* Return "true" if this utility statement will send output to the
|
|
* destination.
|
|
*
|
|
* Generally, there should be a case here for each case in ProcessUtility
|
|
* where "dest" is passed on.
|
|
*/
|
|
bool
|
|
UtilityReturnsTuples(Node *parsetree)
|
|
{
|
|
switch (nodeTag(parsetree))
|
|
{
|
|
case T_FetchStmt:
|
|
{
|
|
FetchStmt *stmt = (FetchStmt *) parsetree;
|
|
Portal portal;
|
|
|
|
if (stmt->ismove)
|
|
return false;
|
|
portal = GetPortalByName(stmt->portalname);
|
|
if (!PortalIsValid(portal))
|
|
return false; /* not our business to raise error */
|
|
return portal->tupDesc ? true : false;
|
|
}
|
|
|
|
case T_ExecuteStmt:
|
|
{
|
|
ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
|
|
PreparedStatement *entry;
|
|
|
|
entry = FetchPreparedStatement(stmt->name, false);
|
|
if (!entry)
|
|
return false; /* not our business to raise error */
|
|
if (entry->plansource->resultDesc)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
case T_ExplainStmt:
|
|
return true;
|
|
|
|
case T_VariableShowStmt:
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* UtilityTupleDescriptor
|
|
* Fetch the actual output tuple descriptor for a utility statement
|
|
* for which UtilityReturnsTuples() previously returned "true".
|
|
*
|
|
* The returned descriptor is created in (or copied into) the current memory
|
|
* context.
|
|
*/
|
|
TupleDesc
|
|
UtilityTupleDescriptor(Node *parsetree)
|
|
{
|
|
switch (nodeTag(parsetree))
|
|
{
|
|
case T_FetchStmt:
|
|
{
|
|
FetchStmt *stmt = (FetchStmt *) parsetree;
|
|
Portal portal;
|
|
|
|
if (stmt->ismove)
|
|
return NULL;
|
|
portal = GetPortalByName(stmt->portalname);
|
|
if (!PortalIsValid(portal))
|
|
return NULL; /* not our business to raise error */
|
|
return CreateTupleDescCopy(portal->tupDesc);
|
|
}
|
|
|
|
case T_ExecuteStmt:
|
|
{
|
|
ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
|
|
PreparedStatement *entry;
|
|
|
|
entry = FetchPreparedStatement(stmt->name, false);
|
|
if (!entry)
|
|
return NULL; /* not our business to raise error */
|
|
return FetchPreparedStatementResultDesc(entry);
|
|
}
|
|
|
|
case T_ExplainStmt:
|
|
return ExplainResultDesc((ExplainStmt *) parsetree);
|
|
|
|
case T_VariableShowStmt:
|
|
{
|
|
VariableShowStmt *n = (VariableShowStmt *) parsetree;
|
|
|
|
return GetPGVariableResultDesc(n->name);
|
|
}
|
|
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* QueryReturnsTuples
|
|
* Return "true" if this Query will send output to the destination.
|
|
*/
|
|
#ifdef NOT_USED
|
|
bool
|
|
QueryReturnsTuples(Query *parsetree)
|
|
{
|
|
switch (parsetree->commandType)
|
|
{
|
|
case CMD_SELECT:
|
|
/* returns tuples */
|
|
return true;
|
|
case CMD_INSERT:
|
|
case CMD_UPDATE:
|
|
case CMD_DELETE:
|
|
/* the forms with RETURNING return tuples */
|
|
if (parsetree->returningList)
|
|
return true;
|
|
break;
|
|
case CMD_UTILITY:
|
|
return UtilityReturnsTuples(parsetree->utilityStmt);
|
|
case CMD_UNKNOWN:
|
|
case CMD_NOTHING:
|
|
/* probably shouldn't get here */
|
|
break;
|
|
}
|
|
return false; /* default */
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* UtilityContainsQuery
|
|
* Return the contained Query, or NULL if there is none
|
|
*
|
|
* Certain utility statements, such as EXPLAIN, contain a plannable Query.
|
|
* This function encapsulates knowledge of exactly which ones do.
|
|
* We assume it is invoked only on already-parse-analyzed statements
|
|
* (else the contained parsetree isn't a Query yet).
|
|
*
|
|
* In some cases (currently, only EXPLAIN of CREATE TABLE AS/SELECT INTO and
|
|
* CREATE MATERIALIZED VIEW), potentially Query-containing utility statements
|
|
* can be nested. This function will drill down to a non-utility Query, or
|
|
* return NULL if none.
|
|
*/
|
|
Query *
|
|
UtilityContainsQuery(Node *parsetree)
|
|
{
|
|
Query *qry;
|
|
|
|
switch (nodeTag(parsetree))
|
|
{
|
|
case T_DeclareCursorStmt:
|
|
qry = castNode(Query, ((DeclareCursorStmt *) parsetree)->query);
|
|
if (qry->commandType == CMD_UTILITY)
|
|
return UtilityContainsQuery(qry->utilityStmt);
|
|
return qry;
|
|
|
|
case T_ExplainStmt:
|
|
qry = castNode(Query, ((ExplainStmt *) parsetree)->query);
|
|
if (qry->commandType == CMD_UTILITY)
|
|
return UtilityContainsQuery(qry->utilityStmt);
|
|
return qry;
|
|
|
|
case T_CreateTableAsStmt:
|
|
qry = castNode(Query, ((CreateTableAsStmt *) parsetree)->query);
|
|
if (qry->commandType == CMD_UTILITY)
|
|
return UtilityContainsQuery(qry->utilityStmt);
|
|
return qry;
|
|
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* AlterObjectTypeCommandTag
|
|
* helper function for CreateCommandTag
|
|
*
|
|
* This covers most cases where ALTER is used with an ObjectType enum.
|
|
*/
|
|
static const char *
|
|
AlterObjectTypeCommandTag(ObjectType objtype)
|
|
{
|
|
const char *tag;
|
|
|
|
switch (objtype)
|
|
{
|
|
case OBJECT_AGGREGATE:
|
|
tag = "ALTER AGGREGATE";
|
|
break;
|
|
case OBJECT_ATTRIBUTE:
|
|
tag = "ALTER TYPE";
|
|
break;
|
|
case OBJECT_CAST:
|
|
tag = "ALTER CAST";
|
|
break;
|
|
case OBJECT_COLLATION:
|
|
tag = "ALTER COLLATION";
|
|
break;
|
|
case OBJECT_COLUMN:
|
|
tag = "ALTER TABLE";
|
|
break;
|
|
case OBJECT_CONVERSION:
|
|
tag = "ALTER CONVERSION";
|
|
break;
|
|
case OBJECT_DATABASE:
|
|
tag = "ALTER DATABASE";
|
|
break;
|
|
case OBJECT_DOMAIN:
|
|
case OBJECT_DOMCONSTRAINT:
|
|
tag = "ALTER DOMAIN";
|
|
break;
|
|
case OBJECT_EXTENSION:
|
|
tag = "ALTER EXTENSION";
|
|
break;
|
|
case OBJECT_FDW:
|
|
tag = "ALTER FOREIGN DATA WRAPPER";
|
|
break;
|
|
case OBJECT_FOREIGN_SERVER:
|
|
tag = "ALTER SERVER";
|
|
break;
|
|
case OBJECT_FOREIGN_TABLE:
|
|
tag = "ALTER FOREIGN TABLE";
|
|
break;
|
|
case OBJECT_FUNCTION:
|
|
tag = "ALTER FUNCTION";
|
|
break;
|
|
case OBJECT_INDEX:
|
|
tag = "ALTER INDEX";
|
|
break;
|
|
case OBJECT_LANGUAGE:
|
|
tag = "ALTER LANGUAGE";
|
|
break;
|
|
case OBJECT_LARGEOBJECT:
|
|
tag = "ALTER LARGE OBJECT";
|
|
break;
|
|
case OBJECT_OPCLASS:
|
|
tag = "ALTER OPERATOR CLASS";
|
|
break;
|
|
case OBJECT_OPERATOR:
|
|
tag = "ALTER OPERATOR";
|
|
break;
|
|
case OBJECT_OPFAMILY:
|
|
tag = "ALTER OPERATOR FAMILY";
|
|
break;
|
|
case OBJECT_POLICY:
|
|
tag = "ALTER POLICY";
|
|
break;
|
|
case OBJECT_ROLE:
|
|
tag = "ALTER ROLE";
|
|
break;
|
|
case OBJECT_RULE:
|
|
tag = "ALTER RULE";
|
|
break;
|
|
case OBJECT_SCHEMA:
|
|
tag = "ALTER SCHEMA";
|
|
break;
|
|
case OBJECT_SEQUENCE:
|
|
tag = "ALTER SEQUENCE";
|
|
break;
|
|
case OBJECT_TABLE:
|
|
case OBJECT_TABCONSTRAINT:
|
|
tag = "ALTER TABLE";
|
|
break;
|
|
case OBJECT_TABLESPACE:
|
|
tag = "ALTER TABLESPACE";
|
|
break;
|
|
case OBJECT_TRIGGER:
|
|
tag = "ALTER TRIGGER";
|
|
break;
|
|
case OBJECT_EVENT_TRIGGER:
|
|
tag = "ALTER EVENT TRIGGER";
|
|
break;
|
|
case OBJECT_TSCONFIGURATION:
|
|
tag = "ALTER TEXT SEARCH CONFIGURATION";
|
|
break;
|
|
case OBJECT_TSDICTIONARY:
|
|
tag = "ALTER TEXT SEARCH DICTIONARY";
|
|
break;
|
|
case OBJECT_TSPARSER:
|
|
tag = "ALTER TEXT SEARCH PARSER";
|
|
break;
|
|
case OBJECT_TSTEMPLATE:
|
|
tag = "ALTER TEXT SEARCH TEMPLATE";
|
|
break;
|
|
case OBJECT_TYPE:
|
|
tag = "ALTER TYPE";
|
|
break;
|
|
case OBJECT_VIEW:
|
|
tag = "ALTER VIEW";
|
|
break;
|
|
case OBJECT_MATVIEW:
|
|
tag = "ALTER MATERIALIZED VIEW";
|
|
break;
|
|
case OBJECT_PUBLICATION:
|
|
tag = "ALTER PUBLICATION";
|
|
break;
|
|
case OBJECT_SUBSCRIPTION:
|
|
tag = "ALTER SUBSCRIPTION";
|
|
break;
|
|
case OBJECT_STATISTIC_EXT:
|
|
tag = "ALTER STATISTICS";
|
|
break;
|
|
default:
|
|
tag = "???";
|
|
break;
|
|
}
|
|
|
|
return tag;
|
|
}
|
|
|
|
/*
|
|
* CreateCommandTag
|
|
* utility to get a string representation of 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.
|
|
*
|
|
* NB: all result strings must be shorter than COMPLETION_TAG_BUFSIZE.
|
|
* Also, the result must point at a true constant (permanent storage).
|
|
*/
|
|
const char *
|
|
CreateCommandTag(Node *parsetree)
|
|
{
|
|
const char *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 = "INSERT";
|
|
break;
|
|
|
|
case T_DeleteStmt:
|
|
tag = "DELETE";
|
|
break;
|
|
|
|
case T_UpdateStmt:
|
|
tag = "UPDATE";
|
|
break;
|
|
|
|
case T_SelectStmt:
|
|
tag = "SELECT";
|
|
break;
|
|
|
|
/* utility statements --- same whether raw or cooked */
|
|
case T_TransactionStmt:
|
|
{
|
|
TransactionStmt *stmt = (TransactionStmt *) parsetree;
|
|
|
|
switch (stmt->kind)
|
|
{
|
|
case TRANS_STMT_BEGIN:
|
|
tag = "BEGIN";
|
|
break;
|
|
|
|
case TRANS_STMT_START:
|
|
tag = "START TRANSACTION";
|
|
break;
|
|
|
|
case TRANS_STMT_COMMIT:
|
|
tag = "COMMIT";
|
|
break;
|
|
|
|
case TRANS_STMT_ROLLBACK:
|
|
case TRANS_STMT_ROLLBACK_TO:
|
|
tag = "ROLLBACK";
|
|
break;
|
|
|
|
case TRANS_STMT_SAVEPOINT:
|
|
tag = "SAVEPOINT";
|
|
break;
|
|
|
|
case TRANS_STMT_RELEASE:
|
|
tag = "RELEASE";
|
|
break;
|
|
|
|
case TRANS_STMT_PREPARE:
|
|
tag = "PREPARE TRANSACTION";
|
|
break;
|
|
|
|
case TRANS_STMT_COMMIT_PREPARED:
|
|
tag = "COMMIT PREPARED";
|
|
break;
|
|
|
|
case TRANS_STMT_ROLLBACK_PREPARED:
|
|
tag = "ROLLBACK PREPARED";
|
|
break;
|
|
|
|
default:
|
|
tag = "???";
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case T_DeclareCursorStmt:
|
|
tag = "DECLARE CURSOR";
|
|
break;
|
|
|
|
case T_ClosePortalStmt:
|
|
{
|
|
ClosePortalStmt *stmt = (ClosePortalStmt *) parsetree;
|
|
|
|
if (stmt->portalname == NULL)
|
|
tag = "CLOSE CURSOR ALL";
|
|
else
|
|
tag = "CLOSE CURSOR";
|
|
}
|
|
break;
|
|
|
|
case T_FetchStmt:
|
|
{
|
|
FetchStmt *stmt = (FetchStmt *) parsetree;
|
|
|
|
tag = (stmt->ismove) ? "MOVE" : "FETCH";
|
|
}
|
|
break;
|
|
|
|
case T_CreateDomainStmt:
|
|
tag = "CREATE DOMAIN";
|
|
break;
|
|
|
|
case T_CreateSchemaStmt:
|
|
tag = "CREATE SCHEMA";
|
|
break;
|
|
|
|
case T_CreateStmt:
|
|
tag = "CREATE TABLE";
|
|
break;
|
|
|
|
case T_CreateTableSpaceStmt:
|
|
tag = "CREATE TABLESPACE";
|
|
break;
|
|
|
|
case T_DropTableSpaceStmt:
|
|
tag = "DROP TABLESPACE";
|
|
break;
|
|
|
|
case T_AlterTableSpaceOptionsStmt:
|
|
tag = "ALTER TABLESPACE";
|
|
break;
|
|
|
|
case T_CreateExtensionStmt:
|
|
tag = "CREATE EXTENSION";
|
|
break;
|
|
|
|
case T_AlterExtensionStmt:
|
|
tag = "ALTER EXTENSION";
|
|
break;
|
|
|
|
case T_AlterExtensionContentsStmt:
|
|
tag = "ALTER EXTENSION";
|
|
break;
|
|
|
|
case T_CreateFdwStmt:
|
|
tag = "CREATE FOREIGN DATA WRAPPER";
|
|
break;
|
|
|
|
case T_AlterFdwStmt:
|
|
tag = "ALTER FOREIGN DATA WRAPPER";
|
|
break;
|
|
|
|
case T_CreateForeignServerStmt:
|
|
tag = "CREATE SERVER";
|
|
break;
|
|
|
|
case T_AlterForeignServerStmt:
|
|
tag = "ALTER SERVER";
|
|
break;
|
|
|
|
case T_CreateUserMappingStmt:
|
|
tag = "CREATE USER MAPPING";
|
|
break;
|
|
|
|
case T_AlterUserMappingStmt:
|
|
tag = "ALTER USER MAPPING";
|
|
break;
|
|
|
|
case T_DropUserMappingStmt:
|
|
tag = "DROP USER MAPPING";
|
|
break;
|
|
|
|
case T_CreateForeignTableStmt:
|
|
tag = "CREATE FOREIGN TABLE";
|
|
break;
|
|
|
|
case T_ImportForeignSchemaStmt:
|
|
tag = "IMPORT FOREIGN SCHEMA";
|
|
break;
|
|
|
|
case T_DropStmt:
|
|
switch (((DropStmt *) parsetree)->removeType)
|
|
{
|
|
case OBJECT_TABLE:
|
|
tag = "DROP TABLE";
|
|
break;
|
|
case OBJECT_SEQUENCE:
|
|
tag = "DROP SEQUENCE";
|
|
break;
|
|
case OBJECT_VIEW:
|
|
tag = "DROP VIEW";
|
|
break;
|
|
case OBJECT_MATVIEW:
|
|
tag = "DROP MATERIALIZED VIEW";
|
|
break;
|
|
case OBJECT_INDEX:
|
|
tag = "DROP INDEX";
|
|
break;
|
|
case OBJECT_TYPE:
|
|
tag = "DROP TYPE";
|
|
break;
|
|
case OBJECT_DOMAIN:
|
|
tag = "DROP DOMAIN";
|
|
break;
|
|
case OBJECT_COLLATION:
|
|
tag = "DROP COLLATION";
|
|
break;
|
|
case OBJECT_CONVERSION:
|
|
tag = "DROP CONVERSION";
|
|
break;
|
|
case OBJECT_SCHEMA:
|
|
tag = "DROP SCHEMA";
|
|
break;
|
|
case OBJECT_TSPARSER:
|
|
tag = "DROP TEXT SEARCH PARSER";
|
|
break;
|
|
case OBJECT_TSDICTIONARY:
|
|
tag = "DROP TEXT SEARCH DICTIONARY";
|
|
break;
|
|
case OBJECT_TSTEMPLATE:
|
|
tag = "DROP TEXT SEARCH TEMPLATE";
|
|
break;
|
|
case OBJECT_TSCONFIGURATION:
|
|
tag = "DROP TEXT SEARCH CONFIGURATION";
|
|
break;
|
|
case OBJECT_FOREIGN_TABLE:
|
|
tag = "DROP FOREIGN TABLE";
|
|
break;
|
|
case OBJECT_EXTENSION:
|
|
tag = "DROP EXTENSION";
|
|
break;
|
|
case OBJECT_FUNCTION:
|
|
tag = "DROP FUNCTION";
|
|
break;
|
|
case OBJECT_AGGREGATE:
|
|
tag = "DROP AGGREGATE";
|
|
break;
|
|
case OBJECT_OPERATOR:
|
|
tag = "DROP OPERATOR";
|
|
break;
|
|
case OBJECT_LANGUAGE:
|
|
tag = "DROP LANGUAGE";
|
|
break;
|
|
case OBJECT_CAST:
|
|
tag = "DROP CAST";
|
|
break;
|
|
case OBJECT_TRIGGER:
|
|
tag = "DROP TRIGGER";
|
|
break;
|
|
case OBJECT_EVENT_TRIGGER:
|
|
tag = "DROP EVENT TRIGGER";
|
|
break;
|
|
case OBJECT_RULE:
|
|
tag = "DROP RULE";
|
|
break;
|
|
case OBJECT_FDW:
|
|
tag = "DROP FOREIGN DATA WRAPPER";
|
|
break;
|
|
case OBJECT_FOREIGN_SERVER:
|
|
tag = "DROP SERVER";
|
|
break;
|
|
case OBJECT_OPCLASS:
|
|
tag = "DROP OPERATOR CLASS";
|
|
break;
|
|
case OBJECT_OPFAMILY:
|
|
tag = "DROP OPERATOR FAMILY";
|
|
break;
|
|
case OBJECT_POLICY:
|
|
tag = "DROP POLICY";
|
|
break;
|
|
case OBJECT_TRANSFORM:
|
|
tag = "DROP TRANSFORM";
|
|
break;
|
|
case OBJECT_ACCESS_METHOD:
|
|
tag = "DROP ACCESS METHOD";
|
|
break;
|
|
case OBJECT_PUBLICATION:
|
|
tag = "DROP PUBLICATION";
|
|
break;
|
|
case OBJECT_STATISTIC_EXT:
|
|
tag = "DROP STATISTICS";
|
|
break;
|
|
default:
|
|
tag = "???";
|
|
}
|
|
break;
|
|
|
|
case T_TruncateStmt:
|
|
tag = "TRUNCATE TABLE";
|
|
break;
|
|
|
|
case T_CommentStmt:
|
|
tag = "COMMENT";
|
|
break;
|
|
|
|
case T_SecLabelStmt:
|
|
tag = "SECURITY LABEL";
|
|
break;
|
|
|
|
case T_CopyStmt:
|
|
tag = "COPY";
|
|
break;
|
|
|
|
case T_RenameStmt:
|
|
tag = AlterObjectTypeCommandTag(((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)->relkind);
|
|
break;
|
|
|
|
case T_AlterDomainStmt:
|
|
tag = "ALTER DOMAIN";
|
|
break;
|
|
|
|
case T_AlterFunctionStmt:
|
|
tag = "ALTER FUNCTION";
|
|
break;
|
|
|
|
case T_GrantStmt:
|
|
{
|
|
GrantStmt *stmt = (GrantStmt *) parsetree;
|
|
|
|
tag = (stmt->is_grant) ? "GRANT" : "REVOKE";
|
|
}
|
|
break;
|
|
|
|
case T_GrantRoleStmt:
|
|
{
|
|
GrantRoleStmt *stmt = (GrantRoleStmt *) parsetree;
|
|
|
|
tag = (stmt->is_grant) ? "GRANT ROLE" : "REVOKE ROLE";
|
|
}
|
|
break;
|
|
|
|
case T_AlterDefaultPrivilegesStmt:
|
|
tag = "ALTER DEFAULT PRIVILEGES";
|
|
break;
|
|
|
|
case T_DefineStmt:
|
|
switch (((DefineStmt *) parsetree)->kind)
|
|
{
|
|
case OBJECT_AGGREGATE:
|
|
tag = "CREATE AGGREGATE";
|
|
break;
|
|
case OBJECT_OPERATOR:
|
|
tag = "CREATE OPERATOR";
|
|
break;
|
|
case OBJECT_TYPE:
|
|
tag = "CREATE TYPE";
|
|
break;
|
|
case OBJECT_TSPARSER:
|
|
tag = "CREATE TEXT SEARCH PARSER";
|
|
break;
|
|
case OBJECT_TSDICTIONARY:
|
|
tag = "CREATE TEXT SEARCH DICTIONARY";
|
|
break;
|
|
case OBJECT_TSTEMPLATE:
|
|
tag = "CREATE TEXT SEARCH TEMPLATE";
|
|
break;
|
|
case OBJECT_TSCONFIGURATION:
|
|
tag = "CREATE TEXT SEARCH CONFIGURATION";
|
|
break;
|
|
case OBJECT_COLLATION:
|
|
tag = "CREATE COLLATION";
|
|
break;
|
|
case OBJECT_ACCESS_METHOD:
|
|
tag = "CREATE ACCESS METHOD";
|
|
break;
|
|
default:
|
|
tag = "???";
|
|
}
|
|
break;
|
|
|
|
case T_CompositeTypeStmt:
|
|
tag = "CREATE TYPE";
|
|
break;
|
|
|
|
case T_CreateEnumStmt:
|
|
tag = "CREATE TYPE";
|
|
break;
|
|
|
|
case T_CreateRangeStmt:
|
|
tag = "CREATE TYPE";
|
|
break;
|
|
|
|
case T_AlterEnumStmt:
|
|
tag = "ALTER TYPE";
|
|
break;
|
|
|
|
case T_ViewStmt:
|
|
tag = "CREATE VIEW";
|
|
break;
|
|
|
|
case T_CreateFunctionStmt:
|
|
tag = "CREATE FUNCTION";
|
|
break;
|
|
|
|
case T_IndexStmt:
|
|
tag = "CREATE INDEX";
|
|
break;
|
|
|
|
case T_RuleStmt:
|
|
tag = "CREATE RULE";
|
|
break;
|
|
|
|
case T_CreateSeqStmt:
|
|
tag = "CREATE SEQUENCE";
|
|
break;
|
|
|
|
case T_AlterSeqStmt:
|
|
tag = "ALTER SEQUENCE";
|
|
break;
|
|
|
|
case T_DoStmt:
|
|
tag = "DO";
|
|
break;
|
|
|
|
case T_CreatedbStmt:
|
|
tag = "CREATE DATABASE";
|
|
break;
|
|
|
|
case T_AlterDatabaseStmt:
|
|
tag = "ALTER DATABASE";
|
|
break;
|
|
|
|
case T_AlterDatabaseSetStmt:
|
|
tag = "ALTER DATABASE";
|
|
break;
|
|
|
|
case T_DropdbStmt:
|
|
tag = "DROP DATABASE";
|
|
break;
|
|
|
|
case T_NotifyStmt:
|
|
tag = "NOTIFY";
|
|
break;
|
|
|
|
case T_ListenStmt:
|
|
tag = "LISTEN";
|
|
break;
|
|
|
|
case T_UnlistenStmt:
|
|
tag = "UNLISTEN";
|
|
break;
|
|
|
|
case T_LoadStmt:
|
|
tag = "LOAD";
|
|
break;
|
|
|
|
case T_ClusterStmt:
|
|
tag = "CLUSTER";
|
|
break;
|
|
|
|
case T_VacuumStmt:
|
|
if (((VacuumStmt *) parsetree)->options & VACOPT_VACUUM)
|
|
tag = "VACUUM";
|
|
else
|
|
tag = "ANALYZE";
|
|
break;
|
|
|
|
case T_ExplainStmt:
|
|
tag = "EXPLAIN";
|
|
break;
|
|
|
|
case T_CreateTableAsStmt:
|
|
switch (((CreateTableAsStmt *) parsetree)->relkind)
|
|
{
|
|
case OBJECT_TABLE:
|
|
if (((CreateTableAsStmt *) parsetree)->is_select_into)
|
|
tag = "SELECT INTO";
|
|
else
|
|
tag = "CREATE TABLE AS";
|
|
break;
|
|
case OBJECT_MATVIEW:
|
|
tag = "CREATE MATERIALIZED VIEW";
|
|
break;
|
|
default:
|
|
tag = "???";
|
|
}
|
|
break;
|
|
|
|
case T_RefreshMatViewStmt:
|
|
tag = "REFRESH MATERIALIZED VIEW";
|
|
break;
|
|
|
|
case T_AlterSystemStmt:
|
|
tag = "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 = "SET";
|
|
break;
|
|
case VAR_RESET:
|
|
case VAR_RESET_ALL:
|
|
tag = "RESET";
|
|
break;
|
|
default:
|
|
tag = "???";
|
|
}
|
|
break;
|
|
|
|
case T_VariableShowStmt:
|
|
tag = "SHOW";
|
|
break;
|
|
|
|
case T_DiscardStmt:
|
|
switch (((DiscardStmt *) parsetree)->target)
|
|
{
|
|
case DISCARD_ALL:
|
|
tag = "DISCARD ALL";
|
|
break;
|
|
case DISCARD_PLANS:
|
|
tag = "DISCARD PLANS";
|
|
break;
|
|
case DISCARD_TEMP:
|
|
tag = "DISCARD TEMP";
|
|
break;
|
|
case DISCARD_SEQUENCES:
|
|
tag = "DISCARD SEQUENCES";
|
|
break;
|
|
default:
|
|
tag = "???";
|
|
}
|
|
break;
|
|
|
|
case T_CreateTransformStmt:
|
|
tag = "CREATE TRANSFORM";
|
|
break;
|
|
|
|
case T_CreateTrigStmt:
|
|
tag = "CREATE TRIGGER";
|
|
break;
|
|
|
|
case T_CreateEventTrigStmt:
|
|
tag = "CREATE EVENT TRIGGER";
|
|
break;
|
|
|
|
case T_AlterEventTrigStmt:
|
|
tag = "ALTER EVENT TRIGGER";
|
|
break;
|
|
|
|
case T_CreatePLangStmt:
|
|
tag = "CREATE LANGUAGE";
|
|
break;
|
|
|
|
case T_CreateRoleStmt:
|
|
tag = "CREATE ROLE";
|
|
break;
|
|
|
|
case T_AlterRoleStmt:
|
|
tag = "ALTER ROLE";
|
|
break;
|
|
|
|
case T_AlterRoleSetStmt:
|
|
tag = "ALTER ROLE";
|
|
break;
|
|
|
|
case T_DropRoleStmt:
|
|
tag = "DROP ROLE";
|
|
break;
|
|
|
|
case T_DropOwnedStmt:
|
|
tag = "DROP OWNED";
|
|
break;
|
|
|
|
case T_ReassignOwnedStmt:
|
|
tag = "REASSIGN OWNED";
|
|
break;
|
|
|
|
case T_LockStmt:
|
|
tag = "LOCK TABLE";
|
|
break;
|
|
|
|
case T_ConstraintsSetStmt:
|
|
tag = "SET CONSTRAINTS";
|
|
break;
|
|
|
|
case T_CheckPointStmt:
|
|
tag = "CHECKPOINT";
|
|
break;
|
|
|
|
case T_ReindexStmt:
|
|
tag = "REINDEX";
|
|
break;
|
|
|
|
case T_CreateConversionStmt:
|
|
tag = "CREATE CONVERSION";
|
|
break;
|
|
|
|
case T_CreateCastStmt:
|
|
tag = "CREATE CAST";
|
|
break;
|
|
|
|
case T_CreateOpClassStmt:
|
|
tag = "CREATE OPERATOR CLASS";
|
|
break;
|
|
|
|
case T_CreateOpFamilyStmt:
|
|
tag = "CREATE OPERATOR FAMILY";
|
|
break;
|
|
|
|
case T_AlterOpFamilyStmt:
|
|
tag = "ALTER OPERATOR FAMILY";
|
|
break;
|
|
|
|
case T_AlterOperatorStmt:
|
|
tag = "ALTER OPERATOR";
|
|
break;
|
|
|
|
case T_AlterTSDictionaryStmt:
|
|
tag = "ALTER TEXT SEARCH DICTIONARY";
|
|
break;
|
|
|
|
case T_AlterTSConfigurationStmt:
|
|
tag = "ALTER TEXT SEARCH CONFIGURATION";
|
|
break;
|
|
|
|
case T_CreatePolicyStmt:
|
|
tag = "CREATE POLICY";
|
|
break;
|
|
|
|
case T_AlterPolicyStmt:
|
|
tag = "ALTER POLICY";
|
|
break;
|
|
|
|
case T_CreateAmStmt:
|
|
tag = "CREATE ACCESS METHOD";
|
|
break;
|
|
|
|
case T_CreatePublicationStmt:
|
|
tag = "CREATE PUBLICATION";
|
|
break;
|
|
|
|
case T_AlterPublicationStmt:
|
|
tag = "ALTER PUBLICATION";
|
|
break;
|
|
|
|
case T_CreateSubscriptionStmt:
|
|
tag = "CREATE SUBSCRIPTION";
|
|
break;
|
|
|
|
case T_AlterSubscriptionStmt:
|
|
tag = "ALTER SUBSCRIPTION";
|
|
break;
|
|
|
|
case T_DropSubscriptionStmt:
|
|
tag = "DROP SUBSCRIPTION";
|
|
break;
|
|
|
|
case T_AlterCollationStmt:
|
|
tag = "ALTER COLLATION";
|
|
break;
|
|
|
|
case T_PrepareStmt:
|
|
tag = "PREPARE";
|
|
break;
|
|
|
|
case T_ExecuteStmt:
|
|
tag = "EXECUTE";
|
|
break;
|
|
|
|
case T_CreateStatsStmt:
|
|
tag = "CREATE STATISTICS";
|
|
break;
|
|
|
|
case T_DeallocateStmt:
|
|
{
|
|
DeallocateStmt *stmt = (DeallocateStmt *) parsetree;
|
|
|
|
if (stmt->name == NULL)
|
|
tag = "DEALLOCATE ALL";
|
|
else
|
|
tag = "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 = "SELECT FOR KEY SHARE";
|
|
break;
|
|
case LCS_FORSHARE:
|
|
tag = "SELECT FOR SHARE";
|
|
break;
|
|
case LCS_FORNOKEYUPDATE:
|
|
tag = "SELECT FOR NO KEY UPDATE";
|
|
break;
|
|
case LCS_FORUPDATE:
|
|
tag = "SELECT FOR UPDATE";
|
|
break;
|
|
default:
|
|
tag = "SELECT";
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
tag = "SELECT";
|
|
break;
|
|
case CMD_UPDATE:
|
|
tag = "UPDATE";
|
|
break;
|
|
case CMD_INSERT:
|
|
tag = "INSERT";
|
|
break;
|
|
case CMD_DELETE:
|
|
tag = "DELETE";
|
|
break;
|
|
case CMD_UTILITY:
|
|
tag = CreateCommandTag(stmt->utilityStmt);
|
|
break;
|
|
default:
|
|
elog(WARNING, "unrecognized commandType: %d",
|
|
(int) stmt->commandType);
|
|
tag = "???";
|
|
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 = "SELECT FOR KEY SHARE";
|
|
break;
|
|
case LCS_FORSHARE:
|
|
tag = "SELECT FOR SHARE";
|
|
break;
|
|
case LCS_FORNOKEYUPDATE:
|
|
tag = "SELECT FOR NO KEY UPDATE";
|
|
break;
|
|
case LCS_FORUPDATE:
|
|
tag = "SELECT FOR UPDATE";
|
|
break;
|
|
default:
|
|
tag = "???";
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
tag = "SELECT";
|
|
break;
|
|
case CMD_UPDATE:
|
|
tag = "UPDATE";
|
|
break;
|
|
case CMD_INSERT:
|
|
tag = "INSERT";
|
|
break;
|
|
case CMD_DELETE:
|
|
tag = "DELETE";
|
|
break;
|
|
case CMD_UTILITY:
|
|
tag = CreateCommandTag(stmt->utilityStmt);
|
|
break;
|
|
default:
|
|
elog(WARNING, "unrecognized commandType: %d",
|
|
(int) stmt->commandType);
|
|
tag = "???";
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
elog(WARNING, "unrecognized node type: %d",
|
|
(int) nodeTag(parsetree));
|
|
tag = "???";
|
|
break;
|
|
}
|
|
|
|
return tag;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetCommandLogLevel
|
|
* utility to get the minimum log_statement level for a command,
|
|
* given either a raw (un-analyzed) parsetree, an analyzed Query,
|
|
* or a PlannedStmt.
|
|
*
|
|
* This must handle all command types, but since the vast majority
|
|
* of 'em are utility commands, it seems sensible to keep it here.
|
|
*/
|
|
LogStmtLevel
|
|
GetCommandLogLevel(Node *parsetree)
|
|
{
|
|
LogStmtLevel lev;
|
|
|
|
switch (nodeTag(parsetree))
|
|
{
|
|
/* recurse if we're given a RawStmt */
|
|
case T_RawStmt:
|
|
lev = GetCommandLogLevel(((RawStmt *) parsetree)->stmt);
|
|
break;
|
|
|
|
/* raw plannable queries */
|
|
case T_InsertStmt:
|
|
case T_DeleteStmt:
|
|
case T_UpdateStmt:
|
|
lev = LOGSTMT_MOD;
|
|
break;
|
|
|
|
case T_SelectStmt:
|
|
if (((SelectStmt *) parsetree)->intoClause)
|
|
lev = LOGSTMT_DDL; /* SELECT INTO */
|
|
else
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
/* utility statements --- same whether raw or cooked */
|
|
case T_TransactionStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_DeclareCursorStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_ClosePortalStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_FetchStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_CreateSchemaStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateStmt:
|
|
case T_CreateForeignTableStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateTableSpaceStmt:
|
|
case T_DropTableSpaceStmt:
|
|
case T_AlterTableSpaceOptionsStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateExtensionStmt:
|
|
case T_AlterExtensionStmt:
|
|
case T_AlterExtensionContentsStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateFdwStmt:
|
|
case T_AlterFdwStmt:
|
|
case T_CreateForeignServerStmt:
|
|
case T_AlterForeignServerStmt:
|
|
case T_CreateUserMappingStmt:
|
|
case T_AlterUserMappingStmt:
|
|
case T_DropUserMappingStmt:
|
|
case T_ImportForeignSchemaStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_DropStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_TruncateStmt:
|
|
lev = LOGSTMT_MOD;
|
|
break;
|
|
|
|
case T_CommentStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_SecLabelStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CopyStmt:
|
|
if (((CopyStmt *) parsetree)->is_from)
|
|
lev = LOGSTMT_MOD;
|
|
else
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_PrepareStmt:
|
|
{
|
|
PrepareStmt *stmt = (PrepareStmt *) parsetree;
|
|
|
|
/* Look through a PREPARE to the contained stmt */
|
|
lev = GetCommandLogLevel(stmt->query);
|
|
}
|
|
break;
|
|
|
|
case T_ExecuteStmt:
|
|
{
|
|
ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
|
|
PreparedStatement *ps;
|
|
|
|
/* Look through an EXECUTE to the referenced stmt */
|
|
ps = FetchPreparedStatement(stmt->name, false);
|
|
if (ps && ps->plansource->raw_parse_tree)
|
|
lev = GetCommandLogLevel(ps->plansource->raw_parse_tree->stmt);
|
|
else
|
|
lev = LOGSTMT_ALL;
|
|
}
|
|
break;
|
|
|
|
case T_DeallocateStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_RenameStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterObjectDependsStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterObjectSchemaStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterOwnerStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterTableMoveAllStmt:
|
|
case T_AlterTableStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterDomainStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_GrantStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_GrantRoleStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterDefaultPrivilegesStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_DefineStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CompositeTypeStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateEnumStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateRangeStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterEnumStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_ViewStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateFunctionStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterFunctionStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_IndexStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_RuleStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateSeqStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterSeqStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_DoStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_CreatedbStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterDatabaseStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterDatabaseSetStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_DropdbStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_NotifyStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_ListenStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_UnlistenStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_LoadStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_ClusterStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_VacuumStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_ExplainStmt:
|
|
{
|
|
ExplainStmt *stmt = (ExplainStmt *) parsetree;
|
|
bool analyze = false;
|
|
ListCell *lc;
|
|
|
|
/* Look through an EXPLAIN ANALYZE to the contained stmt */
|
|
foreach(lc, stmt->options)
|
|
{
|
|
DefElem *opt = (DefElem *) lfirst(lc);
|
|
|
|
if (strcmp(opt->defname, "analyze") == 0)
|
|
analyze = defGetBoolean(opt);
|
|
/* don't "break", as explain.c will use the last value */
|
|
}
|
|
if (analyze)
|
|
return GetCommandLogLevel(stmt->query);
|
|
|
|
/* Plain EXPLAIN isn't so interesting */
|
|
lev = LOGSTMT_ALL;
|
|
}
|
|
break;
|
|
|
|
case T_CreateTableAsStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_RefreshMatViewStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterSystemStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_VariableSetStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_VariableShowStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_DiscardStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_CreateTrigStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateEventTrigStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterEventTrigStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreatePLangStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateDomainStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateRoleStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterRoleStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterRoleSetStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_DropRoleStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_DropOwnedStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_ReassignOwnedStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_LockStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_ConstraintsSetStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_CheckPointStmt:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case T_ReindexStmt:
|
|
lev = LOGSTMT_ALL; /* should this be DDL? */
|
|
break;
|
|
|
|
case T_CreateConversionStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateCastStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateOpClassStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateOpFamilyStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateTransformStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterOpFamilyStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreatePolicyStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterPolicyStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterTSDictionaryStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterTSConfigurationStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateAmStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreatePublicationStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterPublicationStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_CreateSubscriptionStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_AlterSubscriptionStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
case T_DropSubscriptionStmt:
|
|
lev = LOGSTMT_DDL;
|
|
break;
|
|
|
|
/* already-planned queries */
|
|
case T_PlannedStmt:
|
|
{
|
|
PlannedStmt *stmt = (PlannedStmt *) parsetree;
|
|
|
|
switch (stmt->commandType)
|
|
{
|
|
case CMD_SELECT:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case CMD_UPDATE:
|
|
case CMD_INSERT:
|
|
case CMD_DELETE:
|
|
lev = LOGSTMT_MOD;
|
|
break;
|
|
|
|
case CMD_UTILITY:
|
|
lev = GetCommandLogLevel(stmt->utilityStmt);
|
|
break;
|
|
|
|
default:
|
|
elog(WARNING, "unrecognized commandType: %d",
|
|
(int) stmt->commandType);
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* parsed-and-rewritten-but-not-planned queries */
|
|
case T_Query:
|
|
{
|
|
Query *stmt = (Query *) parsetree;
|
|
|
|
switch (stmt->commandType)
|
|
{
|
|
case CMD_SELECT:
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
|
|
case CMD_UPDATE:
|
|
case CMD_INSERT:
|
|
case CMD_DELETE:
|
|
lev = LOGSTMT_MOD;
|
|
break;
|
|
|
|
case CMD_UTILITY:
|
|
lev = GetCommandLogLevel(stmt->utilityStmt);
|
|
break;
|
|
|
|
default:
|
|
elog(WARNING, "unrecognized commandType: %d",
|
|
(int) stmt->commandType);
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
default:
|
|
elog(WARNING, "unrecognized node type: %d",
|
|
(int) nodeTag(parsetree));
|
|
lev = LOGSTMT_ALL;
|
|
break;
|
|
}
|
|
|
|
return lev;
|
|
}
|