
#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/schemacmds.h"
#include "commands/seclabel.h"
#include "commands/sequence.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"

// TODO: Peloton Changes
#include "postmaster/peloton.h"
#include "backend/common/logger.h"
#include "backend/bridge/ddl/ddl.h"
#include "backend/bridge/ddl/ddl_utils.h"

void peloton_ProcessUtility(Node *parsetree, const char *queryString,
                            ProcessUtilityContext context, ParamListInfo params,
                            DestReceiver *dest, char *completionTag);

/* Hook for plugins to get control in ProcessUtility() */
ProcessUtility_hook_type ProcessUtility_hook = NULL;

/* local function declarations */
static void ProcessUtilitySlow(Node *parsetree,
           const char *queryString,
           ProcessUtilityContext context,
           ParamListInfo params,
           DestReceiver *dest,
           char *completionTag,
           Oid *relation_oid = NULL);
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 Query nodes here
 */
bool
CommandIsReadOnly(Node *parsetree)
{
  if (IsA(parsetree, PlannedStmt))
  {
    PlannedStmt *stmt = (PlannedStmt *) parsetree;

    switch (stmt->commandType)
    {
      case CMD_SELECT:
        if (stmt->rowMarks != NIL)
          return false;    /* SELECT FOR [KEY] UPDATE/SHARE */
        else if (stmt->hasModifyingCTE)
          return false;    /* data-modifying CTE */
        else
          return true;
      case CMD_UPDATE:
      case CMD_INSERT:
      case CMD_DELETE:
        return false;
      default:
        elog(WARNING, "unrecognized commandType: %d",
           (int) stmt->commandType);
        break;
    }
  }
  /* For now, treat all utility commands as read/write */
  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_AlterObjectSchemaStmt:
    case T_AlterOwnerStmt:
    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:
      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
 *
 *  parsetree: the parse tree 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
 *  dest: where to send results
 *  completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
 *    in which to store a command completion status string.
 *
 * Notes: as of PG 8.4, 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.
 */
void
ProcessUtility(Node *parsetree,
         const char *queryString,
         ProcessUtilityContext context,
         ParamListInfo params,
         DestReceiver *dest,
         char *completionTag)
{
  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) (parsetree, queryString,
                context, params,
                dest, completionTag);
  else
  {
    elog(DEBUG3, "DDL Query :: %s Type :: %d",
         queryString, nodeTag(parsetree));

    standard_ProcessUtility(parsetree, queryString,
                context, params,
                dest, completionTag);

    elog(DEBUG3, "Postgres process utility done");

    // TODO: Peloton Changes
    if(IsBackend == true)
    {
      peloton_ProcessUtility(parsetree, queryString,
                             context, params,
                             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(Node *parsetree,
            const char *queryString,
            ProcessUtilityContext context,
            ParamListInfo params,
            DestReceiver *dest,
            char *completionTag)
{
  bool    isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);

  check_xact_readonly(parsetree);

  if (completionTag)
    completionTag[0] = '\0';

  switch (nodeTag(parsetree))
  {
      /*
       * ******************** transactions ********************
       */
    case T_TransactionStmt:
      {
        //Peloton change: try to disable this
        //break;
        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 = static_cast<DefElem *>(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
       *
       * Note: DECLARE CURSOR is processed mostly as a SELECT, and
       * therefore what we will get here is a PlannedStmt not a bare
       * DeclareCursorStmt.
       */
    case T_PlannedStmt:
      {
        PlannedStmt *stmt = (PlannedStmt *) parsetree;

        if (stmt->utilityStmt == NULL ||
          !IsA(stmt->utilityStmt, DeclareCursorStmt))
          elog(ERROR, "non-DECLARE CURSOR PlannedStmt passed to ProcessUtility");
        PerformCursorOpen(stmt, 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((CopyStmt *) parsetree, queryString, &processed);
        if (completionTag)
          snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
               "COPY " UINT64_FORMAT, processed);
      }
      break;

    case T_PrepareStmt:
      CheckRestrictedOperation("PREPARE");
      PrepareQuery((PrepareStmt *) parsetree, queryString);
      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");
      Oid db_oid = createdb((CreatedbStmt *) parsetree);

      // TODO: Peloton Changes
      CreatedbStmt* CdbStmt = (CreatedbStmt*) parsetree;
      CdbStmt->database_id = db_oid;
      }
      break;

    case T_AlterDatabaseStmt:
      /* no event triggers for global objects */
      AlterDatabase((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 */
        // Peloton porting issue: the 3rd arg is true
        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:
      {
        // TODO :: Peloton Changes
        // Disabled Vacuumming in Postgres
        if(false){
          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((ExplainStmt *) parsetree, queryString, params, 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((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(parsetree, queryString,
                     context, params,
                     dest, completionTag);
        else
          ExecuteGrantStmt((GrantStmt *) parsetree);
      }
      break;

    case T_DropStmt:
      {
        DropStmt   *stmt = (DropStmt *) parsetree;

        if (EventTriggerSupportsObjectType(stmt->removeType))
          ProcessUtilitySlow(parsetree, queryString,
                     context, params,
                     dest, completionTag);
        else
          ExecDropStmt(stmt, isTopLevel);
      }
      break;

    case T_RenameStmt:
      {
        RenameStmt *stmt = (RenameStmt *) parsetree;

        if (EventTriggerSupportsObjectType(stmt->renameType))
          ProcessUtilitySlow(parsetree, queryString,
                     context, params,
                     dest, completionTag);
        else
          ExecRenameStmt(stmt);
      }
      break;

    case T_AlterObjectSchemaStmt:
      {
        AlterObjectSchemaStmt *stmt = (AlterObjectSchemaStmt *) parsetree;

        if (EventTriggerSupportsObjectType(stmt->objectType))
          ProcessUtilitySlow(parsetree, queryString,
                     context, params,
                     dest, completionTag);
        else
          ExecAlterObjectSchemaStmt(stmt, NULL);
      }
      break;

    case T_AlterOwnerStmt:
      {
        AlterOwnerStmt *stmt = (AlterOwnerStmt *) parsetree;

        if (EventTriggerSupportsObjectType(stmt->objectType))
          ProcessUtilitySlow(parsetree, queryString,
                     context, params,
                     dest, completionTag);
        else
          ExecAlterOwnerStmt(stmt);
      }
      break;

    case T_CommentStmt:
      {
        CommentStmt *stmt = (CommentStmt *) parsetree;

        if (EventTriggerSupportsObjectType(stmt->objtype))
          ProcessUtilitySlow(parsetree, queryString,
                     context, params,
                     dest, completionTag);
        else
          CommentObject((CommentStmt *) parsetree);
        break;
      }

    case T_SecLabelStmt:
      {
        SecLabelStmt *stmt = (SecLabelStmt *) parsetree;

        if (EventTriggerSupportsObjectType(stmt->objtype))
          ProcessUtilitySlow(parsetree, queryString,
                     context, params,
                     dest, completionTag);
        else
          ExecSecLabelStmt(stmt);
        break;
      }

    default:

      /* All other statement types have event trigger support */
      ProcessUtilitySlow(parsetree, queryString,
                 context, params,
                 dest, completionTag);
      break;
  }

}

void
peloton_ProcessUtility(Node *parsetree,
            const char *queryString,
            ProcessUtilityContext context,
            ParamListInfo params,
            DestReceiver *dest,
            char *completionTag)
{
  elog(DEBUG3, "DDL Query : %s", queryString);

  // TODO: Peloton Changes
  peloton_ddl(parsetree);

}

/*
 * 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(Node *parsetree,
           const char *queryString,
           ProcessUtilityContext context,
           ParamListInfo params,
           DestReceiver *dest,
           char *completionTag,
           Oid *relation_oid)
{
  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);
        /*
         * 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);
          // TODO: Peloton Changes
          // Store transformedCreateStmt because we don't want to do this again in Peloton
          ((CreateStmt *)parsetree)->stmts = stmts;

          /* ... 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);
              EventTriggerCollectSimpleCommand(address,
                               secondaryObject,
                               stmt);

              // TODO: Peloton Changes
              ((CreateStmt *)parsetree)->relation_id = address.objectId;

              /*
               * 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);
              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.
               */
              ProcessUtility(stmt,
                       queryString,
                       PROCESS_UTILITY_SUBCOMMAND,
                       params,
                       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);

            // TODO: Peloton Changes
            ((AlterTableStmt *)parsetree)->relation_id = relid;
            ((AlterTableStmt *)parsetree)->stmts = stmts;

            /* ... 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.
                 */
                EventTriggerAlterTableEnd();
                ProcessUtility(stmt,
                         queryString,
                         PROCESS_UTILITY_SUBCOMMAND,
                         params,
                         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(stmt->defnames, stmt->args,
                        stmt->oldstyle,
                        stmt->definition, queryString);
              break;
            case OBJECT_OPERATOR:
              Assert(stmt->args == NIL);
              address = DefineOperator(stmt->defnames,
                            stmt->definition);
              break;
            case OBJECT_TYPE:
              Assert(stmt->args == NIL);
              address = DefineType(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(stmt->defnames,
                             stmt->definition);
              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();

          // TODO: Peloton Changes
          ((IndexStmt *)parsetree)->index_id =  address.objectId;

        }
        break;

      case T_CreateExtensionStmt:
        address = CreateExtension((CreateExtensionStmt *) parsetree);
        break;

      case T_AlterExtensionStmt:
        address = ExecAlterExtensionStmt((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, isTopLevel);
        break;

      case T_ViewStmt:  /* CREATE VIEW */
        EventTriggerAlterTableStart(parsetree);
        address = DefineView((ViewStmt *) parsetree, queryString);
        EventTriggerCollectSimpleCommand(address, secondaryObject,
                         parsetree);
        /* stashed internally */
        commandCollected = true;
        EventTriggerAlterTableEnd();
        break;

      case T_CreateFunctionStmt:  /* CREATE FUNCTION */
        address = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
        break;

      case T_AlterFunctionStmt: /* ALTER FUNCTION */
        address = AlterFunction((AlterFunctionStmt *) parsetree);
        break;

      case T_RuleStmt:  /* CREATE RULE */
        address = DefineRule((RuleStmt *) parsetree, queryString);
        break;

      case T_CreateSeqStmt:
        address = DefineSequence((CreateSeqStmt *) parsetree);
        break;

      case T_AlterSeqStmt:
        address = AlterSequence((AlterSeqStmt *) parsetree);
        break;

      case T_CreateTableAsStmt:
        address = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
                  queryString, params, completionTag);
        break;

      case T_RefreshMatViewStmt:
        /*
         * REFRSH 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:
        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:
        address = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
        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_AlterObjectSchemaStmt:
        address =
          ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
                        &secondaryObject);
        break;

      case T_AlterOwnerStmt:
        address = ExecAlterOwnerStmt((AlterOwnerStmt *) 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((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;

      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 ... unless it's DECLARE CURSOR */
      if (parsetree->utilityStmt == NULL)
        return true;
      break;
    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_ExplainStmt:
      qry = (Query *) ((ExplainStmt *) parsetree)->query;
      Assert(IsA(qry, Query));
      if (qry->commandType == CMD_UTILITY)
        return UtilityContainsQuery(qry->utilityStmt);
      return qry;

    case T_CreateTableAsStmt:
      qry = (Query *) ((CreateTableAsStmt *) parsetree)->query;
      Assert(IsA(qry, 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;
    default:
      tag = "???";
      break;
  }

  return tag;
}

/*
 * CreateCommandTag
 *    utility to get a string representation of the command operation,
 *    given either a raw (un-analyzed) parsetree or a planned query.
 *
 * 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))
  {
      /* 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;
        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_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;
        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_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_PrepareStmt:
      tag = "PREPARE";
      break;

    case T_ExecuteStmt:
      tag = "EXECUTE";
      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->utilityStmt != NULL)
            {
              Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
              tag = "DECLARE CURSOR";
            }
            else 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;
          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->utilityStmt != NULL)
            {
              Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
              tag = "DECLARE CURSOR";
            }
            else 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 or a planned query.
 *
 * 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))
  {
      /* 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);
        else
          lev = LOGSTMT_ALL;
      }
      break;

    case T_DeallocateStmt:
      lev = LOGSTMT_ALL;
      break;

    case T_RenameStmt:
      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;

      /* 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;

          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;
}
